From 0df780015536cd2e31b718caea068cda41458466 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Thu, 6 Mar 2025 14:20:12 +0700 Subject: [PATCH 1/9] add route for profession, provider, and products --- src/routing/AppRoutingSetup.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/routing/AppRoutingSetup.tsx b/src/routing/AppRoutingSetup.tsx index 971fbaa..4730f01 100644 --- a/src/routing/AppRoutingSetup.tsx +++ b/src/routing/AppRoutingSetup.tsx @@ -30,6 +30,9 @@ import PostoAdmsMaster from '@/pages/master/postoadms/PostoAdmsMaster'; import SucosMaster from '@/pages/master/sucos/SucosMaster'; import AldeiasMaster from '@/pages/master/aldeias/AldeiasMaster'; import Municipios from '@/pages/master/municipios/Municipios'; +import ProfessionMaster from '@/pages/master/profession/ProfessionMaster'; +import ProductsMaster from '@/pages/master/products/ProductsMaster'; +import ProviderMaster from '@/pages/master/provider/ProviderMaster'; const AppRoutingSetup = (): ReactElement => { return ( @@ -43,8 +46,14 @@ const AppRoutingSetup = (): ReactElement => { } /> } /> } /> + } /> + } /> + } /> - } /> + } + /> } /> From c0990e4b4d3fa9682779031c334e08f932464c12 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Thu, 6 Mar 2025 14:20:49 +0700 Subject: [PATCH 2/9] fix sorting and add delete dialog --- src/pages/master/sucos/SucosMaster.tsx | 2 + src/pages/master/sucos/hooks/ManageSucos.tsx | 66 ------------------- .../master/sucos/hooks/ManageSucosContext.tsx | 2 +- 3 files changed, 3 insertions(+), 67 deletions(-) delete mode 100644 src/pages/master/sucos/hooks/ManageSucos.tsx diff --git a/src/pages/master/sucos/SucosMaster.tsx b/src/pages/master/sucos/SucosMaster.tsx index 62911a4..9accaf9 100644 --- a/src/pages/master/sucos/SucosMaster.tsx +++ b/src/pages/master/sucos/SucosMaster.tsx @@ -2,6 +2,7 @@ import { Container, DataGridInner } from '@/components'; import { ManageSucosContextProvider } from './hooks/ManageSucosContext'; import AddDialog from './blocks/AddDialog'; import EditDialog from './blocks/EditDialog'; +import DeleteDialog from './blocks/DeleteDialog'; const SucosMaster = () => { return ( @@ -13,6 +14,7 @@ const SucosMaster = () => { + ); diff --git a/src/pages/master/sucos/hooks/ManageSucos.tsx b/src/pages/master/sucos/hooks/ManageSucos.tsx deleted file mode 100644 index cf878fa..0000000 --- a/src/pages/master/sucos/hooks/ManageSucos.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { apiConfig } from '@/config/api.config'; -import React, { createContext, useCallback, useState } from 'react'; - -interface SucosProps { - id: number; - name: string; -} - -interface ContextProps { - sucos: SucosProps[]; - showSearchDialog: boolean; - handleSearchDialog: (show: boolean) => void; - showEditDialog: boolean; - handleEditDialog: (show: boolean, selected_sucos: string | null) => void; - showAddDialog: boolean; - handleAddDialog: (show: boolean) => void; - showDeleteDialog: boolean; - handleDeleteDialog: (show: boolean, selected_sucos: string | null) => void; - selectedSucos: string | null; - getSucosLists: ( - limit: number, - page: number, - with_deleted: boolean, - order_field: any, - order_direction: any - ) => Promise<{ data: SucosProps[]; totalCount: number } | undefined>; -} - -const initialProps: ContextProps = { - sucos: [], - showSearchDialog: false, - handleSearchDialog: () => {}, - showEditDialog: false, - handleEditDialog: () => {}, - showAddDialog: false, - handleAddDialog: () => {}, - showDeleteDialog: false, - handleDeleteDialog: () => {}, - selectedSucos: null, - getSucosLists: async () => undefined -}; - -const ManageSucosContext = createContext(initialProps); -const API_URL = apiConfig.service_master_data; - -const ManageSucosContextProvider = ({ children }: { children: React.ReactNode }) => { - const [sucos, setSucos] = useState([]); - const [showSearchDialog, setShowSearchDialog] = useState(false); - const [showEditDialog, setShowEditDialog] = useState(false); - const [showAddDialog, setShowAddDialog] = useState(false); - const [showDeleteDialog, setShowDeleteDialog] = useState(false); - const [selectedSucos, setSelectedSucos] = useState(null); - - const handleSearchDialog = useCallback((show: boolean) => { - setShowSearchDialog(show); - }, []); - - const handleAddDialog = useCallback((show: boolean) => { - setShowAddDialog(show); - }, []); - - const hanldeEditDialog = useCallback((show: boolean, selected_sucos: string | null) => { - setSelectedSucos(show ? selected_sucos : null); - setShowEditDialog(show); - }, []) -}; diff --git a/src/pages/master/sucos/hooks/ManageSucosContext.tsx b/src/pages/master/sucos/hooks/ManageSucosContext.tsx index 4e8628b..5151803 100644 --- a/src/pages/master/sucos/hooks/ManageSucosContext.tsx +++ b/src/pages/master/sucos/hooks/ManageSucosContext.tsx @@ -142,7 +142,7 @@ const ManageSucosContextProvider = ({ children }: { children: React.ReactNode }) const getSucosLists = async (page: number, limit: number, sorting: any, filter: any) => { try { - sorting: sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; const response = await GetData(`${API_URL}/sucos/list`, { limit, From 24daa3800421c72bd82897717fb8fb4c4a43e9f4 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Thu, 6 Mar 2025 15:14:05 +0700 Subject: [PATCH 3/9] add crud provession --- .../master/profession/ProfessionMaster.tsx | 23 +++ .../master/profession/blocks/AddDialog.tsx | 130 +++++++++++++ .../master/profession/blocks/DeleteDialog.tsx | 86 +++++++++ .../master/profession/blocks/EditDialog.tsx | 131 +++++++++++++ .../master/profession/blocks/ListToolbar.tsx | 57 ++++++ .../hooks/ManageProfessionContext.tsx | 177 ++++++++++++++++++ .../hooks/useManageProfessionContext.tsx | 12 ++ 7 files changed, 616 insertions(+) create mode 100644 src/pages/master/profession/ProfessionMaster.tsx create mode 100644 src/pages/master/profession/blocks/AddDialog.tsx create mode 100644 src/pages/master/profession/blocks/DeleteDialog.tsx create mode 100644 src/pages/master/profession/blocks/EditDialog.tsx create mode 100644 src/pages/master/profession/blocks/ListToolbar.tsx create mode 100644 src/pages/master/profession/hooks/ManageProfessionContext.tsx create mode 100644 src/pages/master/profession/hooks/useManageProfessionContext.tsx diff --git a/src/pages/master/profession/ProfessionMaster.tsx b/src/pages/master/profession/ProfessionMaster.tsx new file mode 100644 index 0000000..68fd122 --- /dev/null +++ b/src/pages/master/profession/ProfessionMaster.tsx @@ -0,0 +1,23 @@ +import { Container, DataGridInner } from '@/components'; +import { ManageProfessionContextProvider } from './hooks/ManageProfessionContext'; +import AddDialog from './blocks/AddDialog'; +import EditDialog from './blocks/EditDialog'; +import DeleteDialog from './blocks/DeleteDialog'; + +const ProfessionMaster = () => { + return ( + + +

Profession

+
+ +
+ + + +
+
+ ); +}; + +export default ProfessionMaster; diff --git a/src/pages/master/profession/blocks/AddDialog.tsx b/src/pages/master/profession/blocks/AddDialog.tsx new file mode 100644 index 0000000..2c71af7 --- /dev/null +++ b/src/pages/master/profession/blocks/AddDialog.tsx @@ -0,0 +1,130 @@ +import { getAuth } from '@/auth'; +import { Alert, useDataGrid } from '@/components'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { useManageProfessionContext } from '../hooks/useManageProfessionContext'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const AddDialog = () => { + const parentRef = useRef(null); + const { reload } = useDataGrid(); + const { PostData } = useCallApi(); + const { showAddDialog, handleAddDialog } = useManageProfessionContext(); + const parsedUser = getAuth()?.user; + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const initialState = { + name: '', + created_by: '', + created_at: '' + }; + const [formField, setFormField] = useState(initialState); + const created_time = new Date(); + const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doCreateProfession = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PostData(`${API_URL}/profession/create`, formField); + + if (response?.status) { + resetForm(); + handleAddDialog(false); + toast.success('Success Create Profession'); + reload(); + } else { + toast.error('Failed Create Profession'); + setAlert({ show: true, message: response?.message }); + } + }, []); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (formField.name === '') { + setAlert({ show: true, message: 'Please fill name field.' }); + return; + } + + // doCreateProfession(e); + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + useEffect(() => { + if (showAddDialog) { + setFormField({ + ...formField, + created_by: parsedUser.username, + created_at: formattedTime + }); + } + }, [formattedTime]); + + return ( + handleAddDialog(open)}> + + + Profession - Create + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+
+ + setFormField({ ...formField, name: e.target.value })} + /> +
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/master/profession/blocks/DeleteDialog.tsx b/src/pages/master/profession/blocks/DeleteDialog.tsx new file mode 100644 index 0000000..0a74fc7 --- /dev/null +++ b/src/pages/master/profession/blocks/DeleteDialog.tsx @@ -0,0 +1,86 @@ +import { apiConfig } from '@/config/api.config'; +import { useManageProfessionContext } from '../hooks/useManageProfessionContext'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { ChangeEvent, useCallback, useState } from 'react'; +import { toast } from 'sonner'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { EnforceSwitch } from '@/components/switch'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const DeleteDialog = () => { + const { showDeleteDialog, handleDeleteDialog, selectedProfession } = useManageProfessionContext(); + const { reload } = useDataGrid(); + const { DeleteData } = useCallApi(); + const [enforce, setEnforce] = useState(false); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const doDeleteProfession = useCallback(async () => { + const response = await DeleteData( + `${API_URL}/profession/delete/${selectedProfession}/${enforce}`, + { + id: selectedProfession + } + ); + + if (response?.status) { + setAlert((prev) => ({ ...prev, show: false, message: '' })); + handleDeleteDialog(false, null); + toast.success('Success Delete Profession'); + reload(); + } else { + toast.error('Failed Delete Profession'); + setAlert((prev) => ({ ...prev, show: true, message: response?.message })); + } + }, [selectedProfession, enforce]); + + return ( + handleDeleteDialog(open, null)}> + + + + + +

Are you sure?

+ you will delete this data! +
+ + ) => { + setEnforce(e.target.checked); + }} + /> +
+
+ {alert.show && ( + +

{alert.message}

+
+ )} +
+ + + + +
+
+ ); +}; + +export default DeleteDialog; diff --git a/src/pages/master/profession/blocks/EditDialog.tsx b/src/pages/master/profession/blocks/EditDialog.tsx new file mode 100644 index 0000000..b8fe390 --- /dev/null +++ b/src/pages/master/profession/blocks/EditDialog.tsx @@ -0,0 +1,131 @@ +import { apiConfig } from '@/config/api.config'; +import { useManageProfessionContext } from '../hooks/useManageProfessionContext'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { getAuth } from '@/auth'; +import React, { useCallback, useEffect, useState } from 'react'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const EditDialog = () => { + const { showEditDialog, handleEditDialog, selectedProfession } = useManageProfessionContext(); + const { reload } = useDataGrid(); + const { PutData } = useCallApi(); + const parsedUser = getAuth()?.user; + const created_time = new Date(); + const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + name: '', + updated_by: '', + updated_at: '' + }; + const [formField, setFormField] = useState(initialState); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doUpdateProfession = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PutData( + `${API_URL}/profession/update/${selectedProfession}`, + formField + ); + + if (response?.status) { + resetForm(); + handleEditDialog(false, null); + toast.success('Success Update Profession'); + reload(); + } else { + toast.error('Failed Update Profession'); + setAlert({ show: true, message: 'Failed Update Profession' }); + } + }, + [selectedProfession, formField] + ); + + const handleUpdate = (e: React.FormEvent) => { + e.preventDefault(); + + if (formField.name === '') { + setAlert({ show: true, message: 'Please fill name field.' }); + return; + } + + // doUpdateProfession(e); + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + useEffect(() => { + if (showEditDialog) { + setFormField({ + ...formField, + updated_by: parsedUser.username, + updated_at: formattedTime + }); + } + }, [formattedTime]); + + return ( + handleEditDialog(open, null)}> + + + Profession - Update + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+
+ + setFormField({ ...formField, name: e.target.value })} + /> +
+
+ +
+ +
+
+
+
+
+
+
+ ); +}; + +export default EditDialog; diff --git a/src/pages/master/profession/blocks/ListToolbar.tsx b/src/pages/master/profession/blocks/ListToolbar.tsx new file mode 100644 index 0000000..b578b42 --- /dev/null +++ b/src/pages/master/profession/blocks/ListToolbar.tsx @@ -0,0 +1,57 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useManageProfessionContext } from '../hooks/useManageProfessionContext'; +import { Button } from '@/components/ui/button'; + +const ListToolbar = () => { + const { reload, table } = useDataGrid(); + const { handleAddDialog } = useManageProfessionContext(); + + return ( +
+
+
+
+ + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/master/profession/hooks/ManageProfessionContext.tsx b/src/pages/master/profession/hooks/ManageProfessionContext.tsx new file mode 100644 index 0000000..d77eabe --- /dev/null +++ b/src/pages/master/profession/hooks/ManageProfessionContext.tsx @@ -0,0 +1,177 @@ +import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { ColumnDef } from '@tanstack/react-table'; +import React, { createContext, useCallback, useMemo, useState } from 'react'; +import { Toaster } from 'sonner'; +import ListToolbar from '../blocks/ListToolbar'; + +interface ProfessionProps { + name: string; +} + +interface ContextProps { + profession: ProfessionProps[]; + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_sucos: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + showDeleteDialog: boolean; + handleDeleteDialog: (show: boolean, selected_sucos: string | null) => void; + selectedProfession: string | null; + getProfessionLists: ( + limit: number, + page: number, + with_deleted: boolean, + order_field: any, + order_direction: any + ) => Promise<{ data: ProfessionProps[]; totalCount: number } | undefined>; +} + +const initialProps: ContextProps = { + profession: [], + showEditDialog: false, + handleEditDialog: () => {}, + showAddDialog: false, + handleAddDialog: () => {}, + showDeleteDialog: false, + handleDeleteDialog: () => {}, + selectedProfession: null, + getProfessionLists: async () => undefined +}; + +const ManageProfessionContext = createContext(initialProps); +const API_URL = apiConfig.service_master_data; + +const ManageProfessionContextProvider = ({ children }: { children: React.ReactNode }) => { + const [profession, setProfession] = useState([]); + const [showAddDialog, setShowAddDialog] = useState(false); + const [showEditDialog, setShowEditDialog] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [selectedProfession, setSelectedProfession] = useState(null); + const { GetData } = useCallApi(); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_profession: string | null) => { + setSelectedProfession(show ? selected_profession : null); + setShowEditDialog(show); + }, []); + + const handleDeleteDialog = useCallback((show: boolean, selected_profession: string | null) => { + setSelectedProfession(show ? selectedProfession : null); + setShowDeleteDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + id: 'actions', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + cell: (data) => { + const row = data.row.original; + return ( + <> + + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleEditDialog, handleDeleteDialog] + ); + + const getProfessionLists = async (page: number, limit: number, sorting: any, filter: any) => { + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; + const response = await GetData(`${API_URL}/profession/list`, { + limit, + page: page + 1, + with_deleted: true, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', + filter: JSON.stringify(filter) + }); + console.log(response?.data); + setProfession(response?.data.list); + return { data: response?.data.list, totalCount: response?.data.total_count }; + } catch (error) { + console.error('Error fetching Profession', error); + } + }; + + return ( + + + + } + layout={{ card: true }} + sorting={[{ id: 'id', desc: false }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + getProfessionLists(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + + ); +}; + +export { ManageProfessionContextProvider, ManageProfessionContext }; +export type { ProfessionProps }; diff --git a/src/pages/master/profession/hooks/useManageProfessionContext.tsx b/src/pages/master/profession/hooks/useManageProfessionContext.tsx new file mode 100644 index 0000000..8fe04af --- /dev/null +++ b/src/pages/master/profession/hooks/useManageProfessionContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageProfessionContext } from './ManageProfessionContext'; + +const useManageProfessionContext = () => { + const context = useContext(ManageProfessionContext); + + if (!context) throw new Error('useManageProfessionContext must be used within AuthProvider'); + + return context; +}; + +export { useManageProfessionContext }; From e4b4b88d590c6c5047e177ae56d811c249b27c45 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 7 Mar 2025 11:37:40 +0700 Subject: [PATCH 4/9] update selected profession --- src/pages/master/profession/hooks/ManageProfessionContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/master/profession/hooks/ManageProfessionContext.tsx b/src/pages/master/profession/hooks/ManageProfessionContext.tsx index d77eabe..c4c4caf 100644 --- a/src/pages/master/profession/hooks/ManageProfessionContext.tsx +++ b/src/pages/master/profession/hooks/ManageProfessionContext.tsx @@ -61,7 +61,7 @@ const ManageProfessionContextProvider = ({ children }: { children: React.ReactNo }, []); const handleDeleteDialog = useCallback((show: boolean, selected_profession: string | null) => { - setSelectedProfession(show ? selectedProfession : null); + setSelectedProfession(show ? selected_profession : null); setShowDeleteDialog(show); }, []); From 7c8d03f7138e8c687de8605a749cff6be26643ca Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Fri, 7 Mar 2025 14:29:30 +0700 Subject: [PATCH 5/9] add crud products --- src/pages/master/products/ProductsMaster.tsx | 23 ++ .../master/products/blocks/AddDialog.tsx | 267 ++++++++++++++++++ .../master/products/blocks/DeleteDialog.tsx | 74 +++++ .../master/products/blocks/EditDialog.tsx | 266 +++++++++++++++++ .../master/products/blocks/ListToolbar.tsx | 55 ++++ .../products/hooks/ManageProductsContext.tsx | 212 ++++++++++++++ .../hooks/useManageProductsContext.tsx | 12 + src/pages/master/provider/ProviderMaster.tsx | 7 + 8 files changed, 916 insertions(+) create mode 100644 src/pages/master/products/ProductsMaster.tsx create mode 100644 src/pages/master/products/blocks/AddDialog.tsx create mode 100644 src/pages/master/products/blocks/DeleteDialog.tsx create mode 100644 src/pages/master/products/blocks/EditDialog.tsx create mode 100644 src/pages/master/products/blocks/ListToolbar.tsx create mode 100644 src/pages/master/products/hooks/ManageProductsContext.tsx create mode 100644 src/pages/master/products/hooks/useManageProductsContext.tsx create mode 100644 src/pages/master/provider/ProviderMaster.tsx diff --git a/src/pages/master/products/ProductsMaster.tsx b/src/pages/master/products/ProductsMaster.tsx new file mode 100644 index 0000000..d4dd679 --- /dev/null +++ b/src/pages/master/products/ProductsMaster.tsx @@ -0,0 +1,23 @@ +import { Container, DataGridInner } from '@/components'; +import { ManageProductsContextProvider } from './hooks/ManageProductsContext'; +import AddDialog from './blocks/AddDialog'; +import EditDialog from './blocks/EditDialog'; +import DeleteDialog from './blocks/DeleteDialog'; + +const ProductsMaster = () => { + return ( + + +

Products

+
+ +
+ + + +
+
+ ); +}; + +export default ProductsMaster; diff --git a/src/pages/master/products/blocks/AddDialog.tsx b/src/pages/master/products/blocks/AddDialog.tsx new file mode 100644 index 0000000..21d77d3 --- /dev/null +++ b/src/pages/master/products/blocks/AddDialog.tsx @@ -0,0 +1,267 @@ +import { getAuth } from '@/auth'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { useManageProductsContext } from '../hooks/useManageProductsContext'; +import { useCallApi } from '@/hooks'; +import { Alert, useDataGrid } from '@/components'; +import { apiConfig } from '@/config/api.config'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const AddDialog = () => { + const parentRef = useRef(null); + const parsedUser = getAuth()?.user; + const { showAddDialog, handleAddDialog } = useManageProductsContext(); + const { PostData } = useCallApi(); + const { reload } = useDataGrid(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + name: '', + description: '', + price_point: 0, + price_cash: 0, + cashback_point: 0, + cashback_cash: 0, + status: '', + providerId: '', + created_by: '', + created_at: '' + }; + const [formField, setFormField] = useState(initialState); + const created_time = new Date(); + const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doCreateProduct = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PostData(`${API_URL}/product/create`, formField); + + if (response?.status) { + resetForm(); + handleAddDialog(false); + toast.success('Success Create Product'); + reload(); + } else { + toast.error('Failed Create Product'); + setAlert({ show: true, message: response?.message }); + } + }, + [formField] + ); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if ( + formField.name === '' || + formField.description === '' || + formField.price_point === 0 || + formField.price_cash === 0 || + formField.cashback_point === 0 || + formField.cashback_cash === 0 || + formField.status === '' || + formField.created_by === '' || + formField.created_at === '' + ) { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + + doCreateProduct(e); + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + useEffect(() => { + if (showAddDialog) { + setFormField({ + ...formField, + created_by: parsedUser.username, + created_at: formattedTime + }); + } + }, [formattedTime]); + + return ( + handleAddDialog(open)}> + + + Product - Create + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+
+ + setFormField({ ...formField, name: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, description: e.target.value })} + /> +
+
+ +
+
+ + { + const value = parseFloat(e.target.value); + setFormField({ ...formField, price_point: isNaN(value) ? 0 : value }); + }} + /> +
+
+ +
+
+ + { + const value = parseFloat(e.target.value); + setFormField({ ...formField, price_cash: isNaN(value) ? 0 : value }); + }} + /> +
+
+ +
+
+ + { + const value = parseFloat(e.target.value); + setFormField({ ...formField, cashback_point: isNaN(value) ? 0 : value }); + }} + /> +
+
+ +
+
+ + { + const value = parseFloat(e.target.value); + setFormField({ ...formField, cashback_cash: isNaN(value) ? 0 : value }); + }} + /> +
+
+ +
+
+ + setFormField({ ...formField, status: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, providerId: e.target.value })} + /> +
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/master/products/blocks/DeleteDialog.tsx b/src/pages/master/products/blocks/DeleteDialog.tsx new file mode 100644 index 0000000..1f09243 --- /dev/null +++ b/src/pages/master/products/blocks/DeleteDialog.tsx @@ -0,0 +1,74 @@ +import { apiConfig } from '@/config/api.config'; +import { useManageProductsContext } from '../hooks/useManageProductsContext'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { ChangeEvent, useCallback, useState } from 'react'; +import { toast } from 'sonner'; +import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@/components/ui/dialog'; +import { EnforceSwitch } from '@/components/switch'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const DeleteDialog = () => { + const { showDeleteDialog, handleDeleteDialog, selectedProducts } = useManageProductsContext(); + const { reload } = useDataGrid(); + const { DeleteData } = useCallApi(); + const [enforce, setEnforce] = useState(false); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const doDeleteProduct = useCallback(async () => { + const response = await DeleteData(`${API_URL}/product/delete/${selectedProducts}/${enforce}`, { + id: selectedProducts + }); + + if (response?.status) { + setAlert((prev) => ({ ...prev, show: false, message: '' })); + handleDeleteDialog(false, null); + toast.success('Success Delete Product'); + reload(); + } else { + toast.error('Failed Delete Product'); + setAlert((prev) => ({ ...prev, show: true, message: response?.message })); + } + }, [selectedProducts, enforce]); + + return ( + handleDeleteDialog(open, null)}> + + + +

Are you sure?

+ you will delete this data! +
+ + ) => { + setEnforce(e.target.checked); + }} + /> +
+
+ {alert.show && ( + +

{alert.message}

+
+ )} +
+ + + + +
+
+ ); +}; + +export default DeleteDialog; diff --git a/src/pages/master/products/blocks/EditDialog.tsx b/src/pages/master/products/blocks/EditDialog.tsx new file mode 100644 index 0000000..5f5e45c --- /dev/null +++ b/src/pages/master/products/blocks/EditDialog.tsx @@ -0,0 +1,266 @@ +import { Alert, useDataGrid } from '@/components'; +import { useManageProductsContext } from '../hooks/useManageProductsContext'; +import { useCallApi } from '@/hooks'; +import { getAuth } from '@/auth'; +import React, { useCallback, useEffect, useState } from 'react'; +import { apiConfig } from '@/config/api.config'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const EditDialog = () => { + const { showEditDialog, handleEditDialog, selectedProducts } = useManageProductsContext(); + const { reload } = useDataGrid(); + const { PutData } = useCallApi(); + const parsedUser = getAuth()?.user; + const created_time = new Date(); + const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + name: '', + description: '', + price_point: 0, + price_cash: 0, + cashback_point: 0, + cashback_cash: 0, + status: '', + providerId: '', + updated_by: '', + updated_at: '' + }; + const [formField, setFormField] = useState(initialState); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doUpdateProduct = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PutData(`${API_URL}/product/update/${selectedProducts}`, formField); + + if (response?.status) { + resetForm(); + handleEditDialog(false, null); + toast.success('Product updated successfully.'); + reload(); + } else { + toast.error('Failed to update Product. Please try again.'); + setAlert({ show: true, message: 'Failed to update Product. Please try again.' }); + } + }, + [selectedProducts, formField] + ); + + const handleUpdate = (e: React.FormEvent) => { + e.preventDefault(); + + if ( + formField.name === '' || + formField.description === '' || + formField.price_point === 0 || + formField.price_cash === 0 || + formField.cashback_point === 0 || + formField.cashback_cash === 0 || + formField.status === '' || + formField.updated_by === '' || + formField.updated_at === '' + ) { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + + doUpdateProduct(e); + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + useEffect(() => { + if (showEditDialog) { + setFormField({ + ...formField, + updated_by: parsedUser.username, + updated_at: formattedTime + }); + } + }, [formattedTime]); + + return ( + handleEditDialog(open, null)}> + + + Product - Update + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+
+ + setFormField({ ...formField, name: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, description: e.target.value })} + /> +
+
+ +
+
+ + { + const value = parseFloat(e.target.value); + setFormField({ ...formField, price_point: isNaN(value) ? 0 : value }); + }} + /> +
+
+ +
+
+ + { + const value = parseFloat(e.target.value); + setFormField({ ...formField, price_cash: isNaN(value) ? 0 : value }); + }} + /> +
+
+ +
+
+ + { + const value = parseFloat(e.target.value); + setFormField({ ...formField, cashback_point: isNaN(value) ? 0 : value }); + }} + /> +
+
+ +
+
+ + { + const value = parseFloat(e.target.value); + setFormField({ ...formField, cashback_cash: isNaN(value) ? 0 : value }); + }} + /> +
+
+ +
+
+ + setFormField({ ...formField, status: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, providerId: e.target.value })} + /> +
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default EditDialog; diff --git a/src/pages/master/products/blocks/ListToolbar.tsx b/src/pages/master/products/blocks/ListToolbar.tsx new file mode 100644 index 0000000..e4af4bd --- /dev/null +++ b/src/pages/master/products/blocks/ListToolbar.tsx @@ -0,0 +1,55 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useManageProductsContext } from '../hooks/useManageProductsContext'; +import { Button } from '@/components/ui/button'; + +const ListToolbar = () => { + const { table, reload } = useDataGrid(); + const { handleAddDialog } = useManageProductsContext(); + + return ( +
+
+
+
+ + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/master/products/hooks/ManageProductsContext.tsx b/src/pages/master/products/hooks/ManageProductsContext.tsx new file mode 100644 index 0000000..c5b819a --- /dev/null +++ b/src/pages/master/products/hooks/ManageProductsContext.tsx @@ -0,0 +1,212 @@ +import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { ColumnDef } from '@tanstack/react-table'; +import React, { createContext, useCallback, useMemo, useState } from 'react'; +import { Toaster } from 'sonner'; +import ListToolbar from '../blocks/ListToolbar'; + +interface ProductsProps { + name: string; + description: string; + price_point: number; + price_cash: number; + status: string; + providerId: string; +} + +interface ContextProps { + products: ProductsProps[]; + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_products: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + showDeleteDialog: boolean; + handleDeleteDialog: (show: boolean, selected_products: string | null) => void; + selectedProducts: string | null; + getProductsLists: ( + limit: number, + page: number, + with_deleted: boolean, + order_field: any, + order_direction: any + ) => Promise<{ data: ProductsProps[]; totalCount: number } | undefined>; +} + +const initialProps: ContextProps = { + products: [], + showEditDialog: false, + handleEditDialog: (show: boolean, selected_products: string | null) => {}, + showAddDialog: false, + handleAddDialog: (show: boolean) => {}, + showDeleteDialog: false, + handleDeleteDialog: (show: boolean, selected_products: string | null) => {}, + selectedProducts: null, + getProductsLists: async () => undefined +}; + +const ManageProductsContext = createContext(initialProps); +const API_URL = apiConfig.service_master_data; + +const ManageProductsContextProvider = ({ children }: { children: React.ReactNode }) => { + const [products, setProducts] = useState([]); + const [showAddDialog, setShowAddDialog] = useState(false); + const [showEditDialog, setShowEditDialog] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [selectedProducts, setSelectedProducts] = useState(null); + const { GetData } = useCallApi(); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_products: string | null) => { + setSelectedProducts(show ? selected_products : null); + setShowEditDialog(show); + }, []); + + const handleDeleteDialog = useCallback((show: boolean, selected_products: string | null) => { + setSelectedProducts(show ? selected_products : null); + setShowDeleteDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.description, + id: 'description', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.price_point, + id: 'price_point', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.price_cash, + id: 'price_cash', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + id: 'actions', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + cell: (data) => { + const row = data.row.original; + return ( + <> + + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleEditDialog, handleDeleteDialog] + ); + + const getProductsLists = async (page: number, limit: number, sorting: any, filter: any) => { + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; + const response = await GetData(`${API_URL}/product/list`, { + limit, + page: page + 1, + with_deleted: true, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', + filter: JSON.stringify(filter) + }); + console.log(response?.data); + setProducts(response?.data.list); + return { data: response?.data.list, totalCount: response?.data.total_count }; + } catch (error) { + console.error('Error fetching Products', error); + } + }; + + return ( + + + + } + layout={{ card: true }} + sorting={[{ id: 'id', desc: false }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + getProductsLists(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + + ); +}; + +export { ManageProductsContext, ManageProductsContextProvider }; +export type { ProductsProps }; diff --git a/src/pages/master/products/hooks/useManageProductsContext.tsx b/src/pages/master/products/hooks/useManageProductsContext.tsx new file mode 100644 index 0000000..58164e6 --- /dev/null +++ b/src/pages/master/products/hooks/useManageProductsContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageProductsContext } from './ManageProductsContext'; + +const useManageProductsContext = () => { + const context = useContext(ManageProductsContext); + if (!context) { + throw new Error('useManageProductsContext must be used within a ManageProductsContextProvider'); + } + return context; +}; + +export { useManageProductsContext }; diff --git a/src/pages/master/provider/ProviderMaster.tsx b/src/pages/master/provider/ProviderMaster.tsx new file mode 100644 index 0000000..8f873b2 --- /dev/null +++ b/src/pages/master/provider/ProviderMaster.tsx @@ -0,0 +1,7 @@ +const ProviderMaster = () => { + return ( +

Provider

+ ) +} + +export default ProviderMaster; \ No newline at end of file From 8c930ae17a8dc817a54cb2ca25557d8fbc167873 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 10 Mar 2025 12:54:48 +0700 Subject: [PATCH 6/9] add crud provider --- src/pages/master/provider/ProviderMaster.tsx | 24 +- .../master/provider/blocks/AddDialog.tsx | 210 +++++++++++++++++ .../master/provider/blocks/DeleteDialog.tsx | 83 +++++++ .../master/provider/blocks/EditDialog.tsx | 202 +++++++++++++++++ .../master/provider/blocks/ListToolbar.tsx | 55 +++++ .../provider/hooks/ManageProviderContext.tsx | 212 ++++++++++++++++++ .../hooks/useManageProviderContext.tsx | 12 + 7 files changed, 794 insertions(+), 4 deletions(-) create mode 100644 src/pages/master/provider/blocks/AddDialog.tsx create mode 100644 src/pages/master/provider/blocks/DeleteDialog.tsx create mode 100644 src/pages/master/provider/blocks/EditDialog.tsx create mode 100644 src/pages/master/provider/blocks/ListToolbar.tsx create mode 100644 src/pages/master/provider/hooks/ManageProviderContext.tsx create mode 100644 src/pages/master/provider/hooks/useManageProviderContext.tsx diff --git a/src/pages/master/provider/ProviderMaster.tsx b/src/pages/master/provider/ProviderMaster.tsx index 8f873b2..a073e82 100644 --- a/src/pages/master/provider/ProviderMaster.tsx +++ b/src/pages/master/provider/ProviderMaster.tsx @@ -1,7 +1,23 @@ +import { Container, DataGridInner } from '@/components'; +import { ManageProviderContextProvider } from './hooks/ManageProviderContext'; +import AddDialog from './blocks/AddDialog'; +import EditDialog from './blocks/EditDialog'; +import DeleteDialog from './blocks/DeleteDialog'; + const ProviderMaster = () => { return ( -

Provider

- ) -} + + +

Manage Provider

+
+ +
+ + + +
+
+ ); +}; -export default ProviderMaster; \ No newline at end of file +export default ProviderMaster; diff --git a/src/pages/master/provider/blocks/AddDialog.tsx b/src/pages/master/provider/blocks/AddDialog.tsx new file mode 100644 index 0000000..39a3260 --- /dev/null +++ b/src/pages/master/provider/blocks/AddDialog.tsx @@ -0,0 +1,210 @@ +import { apiConfig } from '@/config/api.config'; +import { useManageProviderContext } from '../hooks/useManageProviderContext'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { getAuth } from '@/auth'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const AddDialog = () => { + const { showAddDialog, handleAddDialog } = useManageProviderContext(); + const { reload } = useDataGrid(); + const { PostData } = useCallApi(); + const parentRef = useRef(null); + const parsedUser = getAuth()?.user; + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + name: '', + description: '', + type: '', + status: '', + transactionTypeId: '', + agentId: '', + created_by: '', + created_at: '' + }; + const [formField, setFormField] = useState(initialState); + const created_time = new Date(); + const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doCreateProvider = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PostData(`${API_URL}/provider/create`, formField); + + if (response?.status) { + resetForm(); + handleAddDialog(false); + toast.success('Success Create Provider'); + reload(); + } else { + toast.error('Failed Create Provider'); + setAlert({ show: true, message: 'Failed Create Provider' }); + } + }, []); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if ( + formField.name === '' || + formField.description === '' || + formField.status === '' || + formField.transactionTypeId === '' || + formField.agentId === '' + ) { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + + doCreateProvider(e); + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + useEffect(() => { + if (showAddDialog) { + setFormField({ + ...formField, + created_by: parsedUser.username, + created_at: formattedTime + }); + } + }, [formattedTime]); + + return ( + handleAddDialog(open)}> + + + Provider - Create + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+
+ + setFormField({ ...formField, name: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, description: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, type: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, status: e.target.value })} + /> +
+
+ +
+
+ + + setFormField({ ...formField, transactionTypeId: e.target.value }) + } + /> +
+
+ +
+
+ + setFormField({ ...formField, agentId: e.target.value })} + /> +
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/master/provider/blocks/DeleteDialog.tsx b/src/pages/master/provider/blocks/DeleteDialog.tsx new file mode 100644 index 0000000..9be7895 --- /dev/null +++ b/src/pages/master/provider/blocks/DeleteDialog.tsx @@ -0,0 +1,83 @@ +import { apiConfig } from '@/config/api.config'; +import { useManageProviderContext } from '../hooks/useManageProviderContext'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { ChangeEvent, useCallback, useState } from 'react'; +import { toast } from 'sonner'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { EnforceSwitch } from '@/components/switch'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const DeleteDialog = () => { + const { showDeleteDialog, handleDeleteDialog, selectedProvider } = useManageProviderContext(); + const { reload } = useDataGrid(); + const { DeleteData } = useCallApi(); + const [enforce, setEnforce] = useState(false); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const doDeleteProvider = useCallback(async () => { + const response = await DeleteData(`${API_URL}/provider/delete/${selectedProvider}/${enforce}`, { + id: selectedProvider + }); + + if (response?.status) { + setAlert((prev) => ({ ...prev, show: false, message: '' })); + handleDeleteDialog(false, null); + toast.success('Success Delete Provider'); + reload(); + } else { + setAlert((prev) => ({ ...prev, show: true, message: response?.message })); + toast.error('Failed Delete Provider'); + } + }, [selectedProvider, enforce]); + + return ( + handleDeleteDialog(open, null)}> + + + + + +

Are you sure?

+ you will delete this data! +
+ + ) => { + setEnforce(e.target.checked); + }} + /> +
+
+ {alert.show && ( + +

{alert.message}

+
+ )} +
+ + + + +
+
+ ); +}; + +export default DeleteDialog; diff --git a/src/pages/master/provider/blocks/EditDialog.tsx b/src/pages/master/provider/blocks/EditDialog.tsx new file mode 100644 index 0000000..ea5a1e5 --- /dev/null +++ b/src/pages/master/provider/blocks/EditDialog.tsx @@ -0,0 +1,202 @@ +import { apiConfig } from '@/config/api.config'; +import { useManageProviderContext } from '../hooks/useManageProviderContext'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { getAuth } from '@/auth'; +import React, { useCallback, useState } from 'react'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; +const EditDialog = () => { + const { showEditDialog, handleEditDialog, selectedProvider } = useManageProviderContext(); + const { reload } = useDataGrid(); + const { PutData } = useCallApi(); + const parsedUser = getAuth()?.user; + const created_time = new Date(); + const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + name: '', + description: '', + type: '', + status: '', + transactionTypeId: '', + agentId: '', + created_by: '', + created_at: '' + }; + const [formField, setFormField] = useState(initialState); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doUpdateProvider = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PutData(`${API_URL}/provider/update/${selectedProvider}`, formField); + + if (response?.status) { + resetForm(); + handleEditDialog(false, null); + toast.success('Provider updated successfully.'); + reload(); + } else { + toast.error('Failed to update provider.'); + setAlert({ show: true, message: 'Failed to update provider.' }); + } + }, + [selectedProvider, formField] + ); + + const handleUpdate = (e: React.FormEvent) => { + e.preventDefault(); + + if ( + formField.name === '' || + formField.description === '' || + formField.type === '' || + formField.status === '' || + formField.transactionTypeId === '' || + formField.agentId === '' + ) { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + + // doUpdateProvider(e); + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + return ( + handleEditDialog(open, null)}> + + + Provider - Create + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+
+ + setFormField({ ...formField, name: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, description: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, type: e.target.value })} + /> +
+
+ +
+
+ + setFormField({ ...formField, status: e.target.value })} + /> +
+
+ +
+
+ + + setFormField({ ...formField, transactionTypeId: e.target.value }) + } + /> +
+
+ +
+
+ + setFormField({ ...formField, agentId: e.target.value })} + /> +
+
+ +
+ +
+
+
+
+
+
+
+ ); +}; + +export default EditDialog; diff --git a/src/pages/master/provider/blocks/ListToolbar.tsx b/src/pages/master/provider/blocks/ListToolbar.tsx new file mode 100644 index 0000000..24a532f --- /dev/null +++ b/src/pages/master/provider/blocks/ListToolbar.tsx @@ -0,0 +1,55 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useManageProviderContext } from '../hooks/useManageProviderContext'; +import { Button } from '@/components/ui/button'; + +const ListToolbar = () => { + const { reload, table } = useDataGrid(); + const { handleAddDialog } = useManageProviderContext(); + + return ( +
+
+
+
+ + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/master/provider/hooks/ManageProviderContext.tsx b/src/pages/master/provider/hooks/ManageProviderContext.tsx new file mode 100644 index 0000000..10482a6 --- /dev/null +++ b/src/pages/master/provider/hooks/ManageProviderContext.tsx @@ -0,0 +1,212 @@ +import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { ColumnDef } from '@tanstack/react-table'; +import React, { createContext, useCallback, useMemo, useState } from 'react'; +import { Toaster } from 'sonner'; +import ListToolbar from '../blocks/ListToolbar'; + +interface ProviderProps { + name: string; + description: string; + type: string; + status: string; + transactionTypeId: string; + agentId: string; +} + +interface ContextProps { + provider: ProviderProps[]; + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_sucos: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + showDeleteDialog: boolean; + handleDeleteDialog: (show: boolean, selected_sucos: string | null) => void; + selectedProvider: string | null; + getProviderLists: ( + limit: number, + page: number, + with_deleted: boolean, + order_field: any, + order_direction: any + ) => Promise<{ data: ProviderProps[]; totalCount: number } | undefined>; +} + +const initialProps: ContextProps = { + provider: [], + showEditDialog: false, + handleEditDialog: () => {}, + showAddDialog: false, + handleAddDialog: () => {}, + showDeleteDialog: false, + handleDeleteDialog: () => {}, + selectedProvider: null, + getProviderLists: async () => undefined +}; + +const ManageProviderContext = createContext(initialProps); +const API_URL = apiConfig.service_master_data; + +const ManageProviderContextProvider = ({ children }: { children: React.ReactNode }) => { + const [provider, setProvider] = useState([]); + const [showAddDialog, setShowAddDialog] = useState(false); + const [showEditDialog, setShowEditDialog] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [selectedProvider, setSelectedProvider] = useState(null); + const { GetData } = useCallApi(); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_provider: string | null) => { + setShowEditDialog(show); + setSelectedProvider(show ? selected_provider : null); + }, []); + + const handleDeleteDialog = useCallback((show: boolean, selected_provider: string | null) => { + setShowDeleteDialog(show); + setSelectedProvider(show ? selected_provider : null); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.description, + id: 'description', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.type, + id: 'type', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.status, + id: 'status', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + id: 'actions', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + cell: (data) => { + const row = data.row.original; + return ( + <> + + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleEditDialog, handleDeleteDialog] + ); + + const getProviderLists = async (page: number, limit: number, sorting: any, filter: any) => { + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; + const response = await GetData(`${API_URL}/provider/list`, { + limit, + page: page + 1, + with_deleted: true, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', + filter: JSON.stringify(filter) + }); + console.log(response?.data); + setProvider(response?.data.list); + return { data: response?.data.list, totalCount: response?.data.total_count }; + } catch (error) { + console.error('Error fetching Provider', error); + } + }; + + return ( + + + + } + layout={{ card: true }} + sorting={[{ id: 'id', desc: false }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + getProviderLists(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + + ); +}; + +export { ManageProviderContext, ManageProviderContextProvider }; +export type { ProviderProps }; diff --git a/src/pages/master/provider/hooks/useManageProviderContext.tsx b/src/pages/master/provider/hooks/useManageProviderContext.tsx new file mode 100644 index 0000000..1fe8d37 --- /dev/null +++ b/src/pages/master/provider/hooks/useManageProviderContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageProviderContext } from './ManageProviderContext'; + +const useManageProviderContext = () => { + const context = useContext(ManageProviderContext); + + if (!context) throw new Error('useManageProviderContext must be used within AuthProvider'); + + return context; +}; + +export { useManageProviderContext }; From cf7183aa993104a385a8ec9d29a65c12be035d86 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 10 Mar 2025 13:27:51 +0700 Subject: [PATCH 7/9] update fix create and update provider --- .../master/provider/blocks/AddDialog.tsx | 5 ++++- .../master/provider/blocks/EditDialog.tsx | 20 ++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/pages/master/provider/blocks/AddDialog.tsx b/src/pages/master/provider/blocks/AddDialog.tsx index 39a3260..9b7f533 100644 --- a/src/pages/master/provider/blocks/AddDialog.tsx +++ b/src/pages/master/provider/blocks/AddDialog.tsx @@ -68,6 +68,7 @@ const AddDialog = () => { if ( formField.name === '' || formField.description === '' || + formField.type === '' || formField.status === '' || formField.transactionTypeId === '' || formField.agentId === '' @@ -138,7 +139,9 @@ const AddDialog = () => {
- + { status: '', transactionTypeId: '', agentId: '', - created_by: '', - created_at: '' + updated_by: '', + updated_at: '' }; const [formField, setFormField] = useState(initialState); @@ -79,16 +79,26 @@ const EditDialog = () => { return; } - // doUpdateProvider(e); + doUpdateProvider(e); console.log(formField); setAlert({ show: false, message: '' }); }; + useEffect(() => { + if (showEditDialog) { + setFormField({ + ...formField, + updated_by: parsedUser.username, + updated_at: formattedTime + }); + } + }, [formattedTime]); + return ( handleEditDialog(open, null)}> - Provider - Create + Provider - Update From 96d1210e0d36648bed83623df3058de98eaddd60 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 10 Mar 2025 13:28:28 +0700 Subject: [PATCH 8/9] update crud form --- src/pages/master/products/ProductsMaster.tsx | 2 +- src/pages/master/products/blocks/DeleteDialog.tsx | 4 +++- src/pages/master/profession/ProfessionMaster.tsx | 2 +- src/pages/master/profession/blocks/AddDialog.tsx | 2 +- src/pages/master/profession/blocks/EditDialog.tsx | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/master/products/ProductsMaster.tsx b/src/pages/master/products/ProductsMaster.tsx index d4dd679..49f5eee 100644 --- a/src/pages/master/products/ProductsMaster.tsx +++ b/src/pages/master/products/ProductsMaster.tsx @@ -8,7 +8,7 @@ const ProductsMaster = () => { return ( -

Products

+

Manage Products

diff --git a/src/pages/master/products/blocks/DeleteDialog.tsx b/src/pages/master/products/blocks/DeleteDialog.tsx index 1f09243..39c3cb1 100644 --- a/src/pages/master/products/blocks/DeleteDialog.tsx +++ b/src/pages/master/products/blocks/DeleteDialog.tsx @@ -4,7 +4,7 @@ import { Alert, useDataGrid } from '@/components'; import { useCallApi } from '@/hooks'; import { ChangeEvent, useCallback, useState } from 'react'; import { toast } from 'sonner'; -import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@/components/ui/dialog'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { EnforceSwitch } from '@/components/switch'; import { Button } from '@/components/ui/button'; @@ -39,6 +39,8 @@ const DeleteDialog = () => { handleDeleteDialog(open, null)}> + +

Are you sure?

you will delete this data! diff --git a/src/pages/master/profession/ProfessionMaster.tsx b/src/pages/master/profession/ProfessionMaster.tsx index 68fd122..5f43e43 100644 --- a/src/pages/master/profession/ProfessionMaster.tsx +++ b/src/pages/master/profession/ProfessionMaster.tsx @@ -8,7 +8,7 @@ const ProfessionMaster = () => { return ( -

Profession

+

Manage Profession

diff --git a/src/pages/master/profession/blocks/AddDialog.tsx b/src/pages/master/profession/blocks/AddDialog.tsx index 2c71af7..bcf21be 100644 --- a/src/pages/master/profession/blocks/AddDialog.tsx +++ b/src/pages/master/profession/blocks/AddDialog.tsx @@ -66,7 +66,7 @@ const AddDialog = () => { return; } - // doCreateProfession(e); + doCreateProfession(e); console.log(formField); setAlert({ show: false, message: '' }); }; diff --git a/src/pages/master/profession/blocks/EditDialog.tsx b/src/pages/master/profession/blocks/EditDialog.tsx index b8fe390..ad592da 100644 --- a/src/pages/master/profession/blocks/EditDialog.tsx +++ b/src/pages/master/profession/blocks/EditDialog.tsx @@ -70,7 +70,7 @@ const EditDialog = () => { return; } - // doUpdateProfession(e); + doUpdateProfession(e); console.log(formField); setAlert({ show: false, message: '' }); }; From c56554600f3158cc3f7a14834aa07022ec45cab2 Mon Sep 17 00:00:00 2001 From: Wikzyy Date: Mon, 10 Mar 2025 14:27:37 +0700 Subject: [PATCH 9/9] add props onUpdate and onDelete. but not use function yet --- src/pages/account/manage-account/ManageAccount.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/account/manage-account/ManageAccount.tsx b/src/pages/account/manage-account/ManageAccount.tsx index b2a5f31..784a20e 100644 --- a/src/pages/account/manage-account/ManageAccount.tsx +++ b/src/pages/account/manage-account/ManageAccount.tsx @@ -1,10 +1,10 @@ import { apiConfig } from '@/config/api.config'; import { Account, columns } from './Columns'; -import { DataTable } from '@/components/ui/DataTable'; import axios from 'axios'; import { useEffect, useState } from 'react'; import { getData } from '@/utils'; import { useCallApi } from '@/hooks'; +import { DataTable } from '@/components/ui/DataTable'; const API_URL = apiConfig.service_dashboard; @@ -74,7 +74,12 @@ const ManageAccount = () => {

Manage Account

- + console.log('Callback update')} + onDelete={() => console.log('Callback delete')} + />
);