Ringkasan Perubahan

1. Penambahan Pagination pada Fitur Admin Job
     - Menerapkan sistem pagination menggunakan hook usePagination dari hooks/use-pagination.tsx
     - Mengintegrasikan komponen-komponen pagination dari helpers/paginationHelpers.tsx
     - Menambahkan dukungan infinite scroll dan pull-to-refresh
     - Menambahkan loading state, skeleton loader, dan empty state

    2. Pembaruan Fungsi API
     - Memperbarui fungsi apiAdminJob di service/api-admin/api-admin-job.ts untuk mendukung parameter
       pagination
     - Menambahkan parameter page dengan nilai default 1

    3. Modularisasi Kode
     - Memindahkan komponen AdminJobStatus dari app/(application)/admin/job/[status]/status.tsx ke
       screens/Admin/Job/ScreenJobStatus.tsx
     - Mengganti ViewWrapper dengan NewWrapper untuk tampilan yang lebih fleksibel
     - Membuat komponen baru BoxStatusJob.tsx untuk memisahkan logika tampilan item pekerjaan
     - Menggunakan komponen BoxStatusJob di dalam ScreenJobStatus untuk menampilkan daftar pekerjaan

    4. Perbaikan Struktur dan Organisasi Kode
     - Mengorganisir ulang struktur folder untuk komponen admin job
     - Memisahkan tanggung jawab antara komponen layar dan komponen item
     - Mengoptimalkan performa dengan menggunakan useCallback dan useMemo

    File-file yang Diubah

     1. screens/Admin/Job/ScreenJobStatus.tsx - Implementasi utama dengan pagination
     2. screens/Admin/Job/BoxStatusJob.tsx - Komponen baru untuk menampilkan item pekerjaan
     3. service/api-admin/api-admin-job.ts - Penambahan parameter pagination
     4. app/(application)/admin/job/[status]/status.tsx - Diperbarui untuk menggunakan komponen baru

### NO Issue
This commit is contained in:
2026-02-14 11:57:27 +08:00
parent 42fa80c228
commit 1cbe4ab330
7 changed files with 151 additions and 128 deletions

View File

@@ -5,6 +5,7 @@ import {
BaseBox,
DummyLandscapeImage,
Grid,
NewWrapper,
Spacing,
StackCustom,
TextCustom,
@@ -120,7 +121,7 @@ export default function AdminJobDetailStatus() {
return (
<>
<ViewWrapper
<NewWrapper
headerComponent={<AdminBackButtonAntTitle title={`Detail Data`} />}
>
<BaseBox>
@@ -184,7 +185,7 @@ export default function AdminJobDetailStatus() {
/>
)}
<Spacing />
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -2,6 +2,7 @@
import {
AlertDefaultSystem,
BoxButtonOnFooter,
NewWrapper,
TextAreaCustom,
ViewWrapper,
} from "@/components";
@@ -100,7 +101,7 @@ export default function AdminJobRejectInput() {
return (
<>
<ViewWrapper
<NewWrapper
footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Job" />}
>
@@ -112,7 +113,7 @@ export default function AdminJobRejectInput() {
showCount
maxLength={1000}
/>
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -1,117 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
LoaderCustom,
SearchInput,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminJob } from "@/service/api-admin/api-admin-job";
import { Octicons } from "@expo/vector-icons";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
import { Admin_ScreenJobStatus } from "@/screens/Admin/Job/ScreenJobStatus";
export default function AdminJobStatus() {
const { status } = useLocalSearchParams();
const [list, setList] = useState<any | null>(null);
const [loadList, setLoadList] = useState(false);
const [search, setSearch] = useState("");
useFocusEffect(
useCallback(() => {
handlerLoadList();
}, [status, search])
);
const handlerLoadList = async () => {
try {
setLoadList(true);
const response = await apiAdminJob({
category: status as "publish" | "review" | "reject",
search,
});
if (response.success) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const rightComponent = (
<SearchInput
placeholder="Cari"
onChangeText={setSearch}
value={search}
/>
);
return (
<>
<ViewWrapper headerComponent={<AdminTitlePage title="Job Vacancy" />}>
<AdminComp_BoxTitle
title={`${_.startCase(status as string)}`}
rightComponent={rightComponent}
/>
<StackCustom>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Pekerjaan"
/>
{/* <Spacing /> */}
<Divider />
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/job/${item.id}/${status}`);
}}
/>
}
value2={
<TextCustom align="center" truncate={1}>
{item?.Author?.username || "-"}
</TextCustom>
}
value3={
<TextCustom truncate={2} align="center">
{item?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper>
</>
);
return <Admin_ScreenJobStatus />;
}

View File

@@ -61,10 +61,10 @@ Gunakan bahasa indonesia pada cli agar saya mudah membacanya.eclar
<!-- START Prompt Admin Refactoring -->
<!-- Pindah kode ke Screen Component -->
File source: app/(application)/admin/app-information/business-field/[id]/index.tsx
Folder tujuan: screens/Admin/App-Information
Nama file utama: ScreenBusinessFieldDetail.tsx
Nama function utama: Admin_ScreenBusinessFieldDetail
File source: app/(application)/admin/job/[status]/status.tsx
Folder tujuan: screens/Admin/Job
Nama file utama: ScreenJobStatus.tsx
Nama function utama: Admin_ScreenJobStatus
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Nama function utama" kemudian clean code, import dan panggil function tersebut pada file "File source"
@@ -72,15 +72,14 @@ Analisa juga file "Nama file utama" , jika belum menggunakan NewWrapper pada fil
<!-- Penerapan Pagination -->
Function fecth: apiAdminMasterBank
File function fetch: service/api-admin/api-master-admin.ts
Function fecth: apiAdminJob
File function fetch: service/api-admin/api-admin-job.ts
Terapkan pagination pada file "Nama file utama"
Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx
Perbaiki fetch "Function fecth" , pada file "File function fetch"
Jika tidak ada props page maka tambahkan props page dan default page: "1"
Jika tidak ada props page maka tambahkan props page dan default page: "1" ( string )
Kemudian rapikan code nya pisah komponen seperti render item dan lainnya agar lebih rapi dan di dalam return panggil komponen tersebut
Gunakan bahasa indonesia pada cli agar saya mudah membacanya.
<!-- END Prompt Admin Refactoring -->

View File

@@ -0,0 +1,29 @@
import { Spacing, StackCustom, TextCustom } from "@/components";
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
import { router } from "expo-router";
import { View } from "react-native";
import { Divider } from "react-native-paper";
interface BoxStatusJobProps {
item: any;
status: string;
}
export function BoxStatusJob({ item, status }: BoxStatusJobProps) {
return (
<AdminBasicBox
style={{ marginHorizontal: 10, marginVertical: 5 }}
onPress={() => {
router.push(`/admin/job/${item.id}/${status}`);
}}
>
<StackCustom>
<View style={{paddingBlock: 8}}>
<TextCustom size={"large"} align="center" bold truncate={2}>
{item?.title || "-"}
</TextCustom>
</View>
</StackCustom>
</AdminBasicBox>
);
}

View File

@@ -0,0 +1,103 @@
import { SearchInput } from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { MainColor } from "@/constants/color-palet";
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { usePagination } from "@/hooks/use-pagination";
import { apiAdminJob } from "@/service/api-admin/api-admin-job";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useMemo, useState } from "react";
import { RefreshControl } from "react-native";
import { Divider } from "react-native-paper";
import { BoxStatusJob } from "./BoxStatusJob";
export function Admin_ScreenJobStatus() {
const { status } = useLocalSearchParams();
const [search, setSearch] = useState("");
// Gunakan hook pagination
const pagination = usePagination({
fetchFunction: async (page, searchQuery) => {
const response = await apiAdminJob({
category: status as "publish" | "review" | "reject",
search: searchQuery,
page: String(page),
});
if (response.success) {
return { data: response.data };
} else {
return { data: [] };
}
},
pageSize: PAGINATION_DEFAULT_TAKE,
searchQuery: search,
dependencies: [status],
});
// Komponen kanan untuk header
const rightComponent = useMemo(
() => (
<SearchInput
placeholder="Cari perkerjaan"
onChangeText={setSearch}
value={search}
/>
),
[search],
);
// Render item untuk daftar pekerjaan
const renderItem = useCallback(
({ item, index }: { item: any; index: number }) => (
<BoxStatusJob key={index} item={item} status={status as string} />
),
[status],
);
const headerComponent = useMemo(
() => (
<AdminComp_BoxTitle
title={`Job ${_.startCase(status as string)}`}
rightComponent={rightComponent}
/>
),
[status, rightComponent],
);
// Buat komponen-komponen pagination
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
searchQuery: search,
emptyMessage: "Tidak ada data",
emptySearchMessage: "Tidak ada hasil pencarian",
isInitialLoad: pagination.isInitialLoad,
skeletonCount: PAGINATION_DEFAULT_TAKE,
skeletonHeight: 100,
});
return (
<NewWrapper
listData={pagination.listData}
renderItem={renderItem}
keyExtractor={(item: any) => item.id.toString()}
headerComponent={headerComponent}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onEndReached={pagination.loadMore}
refreshControl={
<RefreshControl
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
/>
}
/>
);
}

View File

@@ -3,13 +3,15 @@ import { apiConfig } from "../api-config";
export async function apiAdminJob({
category,
search,
page = "1",
}: {
category: "dashboard" | "publish" | "review" | "reject";
search?: string;
page?: string;
}) {
try {
const response = await apiConfig.get(
`/mobile/admin/job?category=${category}&search=${search}`
`/mobile/admin/job?category=${category}&search=${search}&page=${page}`
);
return response.data;
} catch (error) {