Sinkronisasi UI & API Admin - User Submenu lowongan kerja lokal, Menu Ekonomi
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -61,13 +62,39 @@ const lowonganKerjaState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.LowonganPekerjaanGetPayload<{
|
| Prisma.LowonganPekerjaanGetPayload<{
|
||||||
omit: { isActive: true };
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
lowonganKerjaState.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
lowonganKerjaState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
lowonganKerjaState.findMany.page = page;
|
||||||
|
lowonganKerjaState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
lowonganKerjaState.findMany.data = res.data.data ?? [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
lowonganKerjaState.findMany.data = [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch lowongan kerja paginated:", err);
|
||||||
|
lowonganKerjaState.findMany.data = [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
lowonganKerjaState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,20 +30,21 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
const lowonganState = useProxy(lowonganKerjaState)
|
const lowonganState = useProxy(lowonganKerjaState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = lowonganState.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
lowonganState.findMany.load();
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (lowonganState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.posisi.toLowerCase().includes(keyword) ||
|
|
||||||
item.namaPerusahaan.toLowerCase().includes(keyword) ||
|
|
||||||
item.lokasi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!lowonganState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -60,18 +61,24 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Bekerja Sebagai</TableTh>
|
<TableTh>Pekerjaan</TableTh>
|
||||||
<TableTh>Nama Usaha</TableTh>
|
<TableTh>Nama Perusahaan</TableTh>
|
||||||
<TableTh>Alamat Usaha</TableTh>
|
<TableTh>Lokasi</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.posisi}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.namaPerusahaan}</TableTd>
|
<Text fz={"md"}>{item.posisi}</Text>
|
||||||
<TableTd>{item.lokasi}</TableTd>
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz={"md"}>{item.namaPerusahaan}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz={"md"}>{item.lokasi}</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
@@ -82,6 +89,14 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,55 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
// /api/berita/findManyPaginated.ts
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function lowonganKerjaFindMany() {
|
async function lowonganKerjaFindMany(context: Context) {
|
||||||
try {
|
// Ambil parameter dari query
|
||||||
const data = await prisma.lowonganPekerjaan.findMany({
|
const page = Number(context.query.page) || 1;
|
||||||
where: { isActive: true },
|
const limit = Number(context.query.limit) || 10;
|
||||||
});
|
const search = (context.query.search as string) || '';
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
return {
|
// Buat where clause
|
||||||
success: true,
|
const where: any = { isActive: true };
|
||||||
message: "Success fetch lowongan kerja",
|
|
||||||
data,
|
// Tambahkan pencarian (jika ada)
|
||||||
};
|
if (search) {
|
||||||
} catch (e) {
|
where.OR = [
|
||||||
console.error("Find many error:", e);
|
{ posisi: { contains: search, mode: 'insensitive' } },
|
||||||
return {
|
{ namaPerusahaan: { contains: search, mode: 'insensitive' } },
|
||||||
success: false,
|
{ lokasi: { contains: search, mode: 'insensitive' } },
|
||||||
message: "Failed fetch lowongan kerja",
|
];
|
||||||
};
|
}
|
||||||
}
|
|
||||||
}
|
try {
|
||||||
|
// Ambil data dan total count secara paralel
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
prisma.lowonganPekerjaan.findMany({
|
||||||
|
where,
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
}),
|
||||||
|
prisma.lowonganPekerjaan.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil ambil lowongan kerja dengan pagination",
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di findMany paginated:", e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Gagal mengambil data lowongan kerja",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default lowonganKerjaFindMany;
|
||||||
@@ -1,64 +1,39 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Box, Text, TextInput, Group, SimpleGrid, Paper, Flex, Button } from '@mantine/core';
|
import { Stack, Box, Text, TextInput, Group, SimpleGrid, Paper, Flex, Button, Skeleton, Center, Pagination } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
import { IconBriefcase, IconClock, IconMapPin, IconSearch } from '@tabler/icons-react';
|
import { IconBriefcase, IconClock, IconMapPin, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
kerja: 'Kasir',
|
|
||||||
tempat: 'Toko Sumber Rejeki',
|
|
||||||
alamat: 'Desa Munggu , Badung',
|
|
||||||
gaji: 'Rp. 2.500.000 / bulan'
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
kerja: 'Kasir',
|
|
||||||
tempat: 'Toko Sumber Rejeki',
|
|
||||||
alamat: 'Desa Munggu , Badung',
|
|
||||||
gaji: 'Rp. 2.500.000 / bulan'
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
kerja: 'Kasir',
|
|
||||||
tempat: 'Toko Sumber Rejeki',
|
|
||||||
alamat: 'Desa Munggu , Badung',
|
|
||||||
gaji: 'Rp. 2.500.000 / bulan'
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
kerja: 'Kasir',
|
|
||||||
tempat: 'Toko Sumber Rejeki',
|
|
||||||
alamat: 'Desa Munggu , Badung',
|
|
||||||
gaji: 'Rp. 2.500.000 / bulan'
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
kerja: 'Kasir',
|
|
||||||
tempat: 'Toko Sumber Rejeki',
|
|
||||||
alamat: 'Desa Munggu , Badung',
|
|
||||||
gaji: 'Rp. 2.500.000 / bulan'
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
kerja: 'Kasir',
|
|
||||||
tempat: 'Toko Sumber Rejeki',
|
|
||||||
alamat: 'Desa Munggu , Badung',
|
|
||||||
gaji: 'Rp. 2.500.000 / bulan'
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const state = useProxy(lowonganKerjaState)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = state.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 6, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||||
<Box px={{ base: 'md', md: 100 }}>
|
<Box px={{ base: 'md', md: 100 }}>
|
||||||
@@ -74,6 +49,8 @@ function Page() {
|
|||||||
w={{ base: 500, md: 700 }}
|
w={{ base: 500, md: 700 }}
|
||||||
placeholder='Cari Pekerjaan'
|
placeholder='Cari Pekerjaan'
|
||||||
leftSection={<IconSearch size={20} />}
|
leftSection={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -93,15 +70,15 @@ function Page() {
|
|||||||
<Flex gap={'xl'} align={'center'}>
|
<Flex gap={'xl'} align={'center'}>
|
||||||
<IconBriefcase color={colors['blue-button']} size={50} />
|
<IconBriefcase color={colors['blue-button']} size={50} />
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>{v.kerja}</Text>
|
<Text fw={'bold'} fz={'h4'} c={colors['blue-button']}>{v.posisi}</Text>
|
||||||
<Text fz={'h4'}>{v.tempat}</Text>
|
<Text fz={'h4'}>{v.namaPerusahaan}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={'xl'} align={'center'}>
|
<Flex gap={'xl'} align={'center'}>
|
||||||
<IconMapPin color={colors['blue-button']} size={50} />
|
<IconMapPin color={colors['blue-button']} size={50} />
|
||||||
<Text fz={'h4'}>{v.alamat}</Text>
|
<Text fz={'h4'}>{v.lokasi}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
@@ -119,6 +96,14 @@ function Page() {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box >
|
||||||
</Stack >
|
</Stack >
|
||||||
|
|||||||
Reference in New Issue
Block a user