From 1cbe4ab330952f984ff03ef34c11e730d859d4c3 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Sat, 14 Feb 2026 11:57:27 +0800 Subject: [PATCH] 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 --- .../admin/job/[id]/[status]/index.tsx | 5 +- .../admin/job/[id]/[status]/reject-input.tsx | 5 +- .../admin/job/[status]/status.tsx | 116 +----------------- docs/prompt-for-qwen-code.md | 17 ++- screens/Admin/Job/BoxStatusJob.tsx | 29 +++++ screens/Admin/Job/ScreenJobStatus.tsx | 103 ++++++++++++++++ service/api-admin/api-admin-job.ts | 4 +- 7 files changed, 151 insertions(+), 128 deletions(-) create mode 100644 screens/Admin/Job/BoxStatusJob.tsx create mode 100644 screens/Admin/Job/ScreenJobStatus.tsx diff --git a/app/(application)/admin/job/[id]/[status]/index.tsx b/app/(application)/admin/job/[id]/[status]/index.tsx index d1a6d0a..75a8429 100644 --- a/app/(application)/admin/job/[id]/[status]/index.tsx +++ b/app/(application)/admin/job/[id]/[status]/index.tsx @@ -5,6 +5,7 @@ import { BaseBox, DummyLandscapeImage, Grid, + NewWrapper, Spacing, StackCustom, TextCustom, @@ -120,7 +121,7 @@ export default function AdminJobDetailStatus() { return ( <> - } > @@ -184,7 +185,7 @@ export default function AdminJobDetailStatus() { /> )} - + ); } diff --git a/app/(application)/admin/job/[id]/[status]/reject-input.tsx b/app/(application)/admin/job/[id]/[status]/reject-input.tsx index d3b9766..7454d82 100644 --- a/app/(application)/admin/job/[id]/[status]/reject-input.tsx +++ b/app/(application)/admin/job/[id]/[status]/reject-input.tsx @@ -2,6 +2,7 @@ import { AlertDefaultSystem, BoxButtonOnFooter, + NewWrapper, TextAreaCustom, ViewWrapper, } from "@/components"; @@ -100,7 +101,7 @@ export default function AdminJobRejectInput() { return ( <> - } > @@ -112,7 +113,7 @@ export default function AdminJobRejectInput() { showCount maxLength={1000} /> - + ); } diff --git a/app/(application)/admin/job/[status]/status.tsx b/app/(application)/admin/job/[status]/status.tsx index 530aefa..e93b2c0 100644 --- a/app/(application)/admin/job/[status]/status.tsx +++ b/app/(application)/admin/job/[status]/status.tsx @@ -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(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 = ( - - ); - return ( - <> - }> - - - - - {/* */} - - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - Tidak ada data - - ) : ( - list?.map((item: any, index: number) => ( - - } - onPress={() => { - router.push(`/admin/job/${item.id}/${status}`); - }} - /> - } - value2={ - - {item?.Author?.username || "-"} - - } - value3={ - - {item?.title || "-"} - - } - /> - )) - )} - - - - ); + return ; } diff --git a/docs/prompt-for-qwen-code.md b/docs/prompt-for-qwen-code.md index 022a6d8..b33e795 100644 --- a/docs/prompt-for-qwen-code.md +++ b/docs/prompt-for-qwen-code.md @@ -61,10 +61,10 @@ Gunakan bahasa indonesia pada cli agar saya mudah membacanya.eclar -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 -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. diff --git a/screens/Admin/Job/BoxStatusJob.tsx b/screens/Admin/Job/BoxStatusJob.tsx new file mode 100644 index 0000000..768f48f --- /dev/null +++ b/screens/Admin/Job/BoxStatusJob.tsx @@ -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 ( + { + router.push(`/admin/job/${item.id}/${status}`); + }} + > + + + + {item?.title || "-"} + + + + + ); +} \ No newline at end of file diff --git a/screens/Admin/Job/ScreenJobStatus.tsx b/screens/Admin/Job/ScreenJobStatus.tsx new file mode 100644 index 0000000..d63aadf --- /dev/null +++ b/screens/Admin/Job/ScreenJobStatus.tsx @@ -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( + () => ( + + ), + [search], + ); + + // Render item untuk daftar pekerjaan + const renderItem = useCallback( + ({ item, index }: { item: any; index: number }) => ( + + ), + [status], + ); + + const headerComponent = useMemo( + () => ( + + ), + [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 ( + item.id.toString()} + headerComponent={headerComponent} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + onEndReached={pagination.loadMore} + refreshControl={ + + } + /> + ); +} diff --git a/service/api-admin/api-admin-job.ts b/service/api-admin/api-admin-job.ts index 2a8a619..4c3953f 100644 --- a/service/api-admin/api-admin-job.ts +++ b/service/api-admin/api-admin-job.ts @@ -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) {