From a5d22db27b2fc920a11443700b052f1b6caf3b04 Mon Sep 17 00:00:00 2001 From: ferdiansyah783 Date: Thu, 7 Aug 2025 14:31:42 +0700 Subject: [PATCH] feat: payment & customer --- .../inventory}/adjustment/page.tsx | 3 +- .../inventory}/list/page.tsx | 3 +- .../ecommerce/products/ingredients/page.tsx | 4 +- .../finance/payment-methods/list/page.tsx | 25 ++ .../apps/organization/outlets/list/page.tsx | 26 ++ .../layout/vertical/VerticalMenu.tsx | 22 +- src/data/dictionaries/ar.json | 4 + src/data/dictionaries/en.json | 4 + src/data/dictionaries/fr.json | 4 + src/redux-store/index.ts | 4 +- src/redux-store/slices/paymentMethod.ts | 37 ++ src/services/mutations/paymentMethods.ts | 52 +++ src/services/mutations/products.ts | 2 + src/services/queries/paymentMethods.ts | 36 ++ src/types/services/order.ts | 6 +- src/types/services/paymentMethod.ts | 8 +- src/utils/transform.ts | 17 + .../customers/list/AddCustomerDrawer.tsx | 252 ++++------- .../customers/list/CustomerListTable.tsx | 399 +++++++++-------- .../ecommerce/orders/list/OrderListTable.tsx | 127 +----- .../products/add/ProductInformation.tsx | 2 +- .../products/add/ProductVariants.tsx | 77 ++-- .../category/ProductCategoryTable.tsx | 14 +- .../products/detail/ProductDetail.tsx | 52 +-- .../ingredient/ProductIngredientTable.tsx | 393 +++++++++++++++++ .../products/list/ProductListTable.tsx | 33 +- .../products/units/ProductUnitTable.tsx | 16 +- .../adjustment/AdjustmentStockDrawer.tsx | 8 +- .../stock/adjustment/StockListTable.tsx | 8 +- .../stock/list/AddStockDrawer.tsx | 8 +- .../stock/list/StockListTable.tsx | 16 +- .../stock/list/TableFilters.tsx | 7 +- .../list/AddPaymentMethodDrawer.tsx | 167 ++++++++ .../list/PaymentMethodListTable.tsx | 401 ++++++++++++++++++ .../list/OrganizationOutletListTable.tsx | 395 +++++++++++++++++ 35 files changed, 2033 insertions(+), 599 deletions(-) rename src/app/[lang]/(dashboard)/(private)/apps/{stock => ecommerce/inventory}/adjustment/page.tsx (88%) rename src/app/[lang]/(dashboard)/(private)/apps/{stock => ecommerce/inventory}/list/page.tsx (88%) create mode 100644 src/app/[lang]/(dashboard)/(private)/apps/finance/payment-methods/list/page.tsx create mode 100644 src/app/[lang]/(dashboard)/(private)/apps/organization/outlets/list/page.tsx create mode 100644 src/redux-store/slices/paymentMethod.ts create mode 100644 src/services/mutations/paymentMethods.ts create mode 100644 src/services/queries/paymentMethods.ts create mode 100644 src/utils/transform.ts create mode 100644 src/views/apps/ecommerce/products/ingredient/ProductIngredientTable.tsx rename src/views/apps/{ => ecommerce}/stock/adjustment/AdjustmentStockDrawer.tsx (94%) rename src/views/apps/{ => ecommerce}/stock/adjustment/StockListTable.tsx (97%) rename src/views/apps/{ => ecommerce}/stock/list/AddStockDrawer.tsx (94%) rename src/views/apps/{ => ecommerce}/stock/list/StockListTable.tsx (95%) rename src/views/apps/{ => ecommerce}/stock/list/TableFilters.tsx (95%) create mode 100644 src/views/apps/finance/payment-methods/list/AddPaymentMethodDrawer.tsx create mode 100644 src/views/apps/finance/payment-methods/list/PaymentMethodListTable.tsx create mode 100644 src/views/apps/organization/outlets/list/OrganizationOutletListTable.tsx diff --git a/src/app/[lang]/(dashboard)/(private)/apps/stock/adjustment/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/inventory/adjustment/page.tsx similarity index 88% rename from src/app/[lang]/(dashboard)/(private)/apps/stock/adjustment/page.tsx rename to src/app/[lang]/(dashboard)/(private)/apps/ecommerce/inventory/adjustment/page.tsx index e318966..f685b81 100644 --- a/src/app/[lang]/(dashboard)/(private)/apps/stock/adjustment/page.tsx +++ b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/inventory/adjustment/page.tsx @@ -1,7 +1,8 @@ // MUI Imports +import StockListTable from "../../../../../../../../views/apps/ecommerce/stock/adjustment/StockListTable" + // Component Imports -import StockListTable from '../../../../../../../views/apps/stock/adjustment/StockListTable' // Data Imports diff --git a/src/app/[lang]/(dashboard)/(private)/apps/stock/list/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/inventory/list/page.tsx similarity index 88% rename from src/app/[lang]/(dashboard)/(private)/apps/stock/list/page.tsx rename to src/app/[lang]/(dashboard)/(private)/apps/ecommerce/inventory/list/page.tsx index d5b379b..391a59a 100644 --- a/src/app/[lang]/(dashboard)/(private)/apps/stock/list/page.tsx +++ b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/inventory/list/page.tsx @@ -1,7 +1,6 @@ // MUI Imports -// Component Imports -import StockListTable from '../../../../../../../views/apps/stock/list/StockListTable' +import StockListTable from "../../../../../../../../views/apps/ecommerce/stock/list/StockListTable" // Data Imports diff --git a/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/products/ingredients/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/products/ingredients/page.tsx index 19d4fe2..242e73b 100644 --- a/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/products/ingredients/page.tsx +++ b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/products/ingredients/page.tsx @@ -1,8 +1,8 @@ // Component Imports -import ProductUnitTable from '../../../../../../../../views/apps/ecommerce/products/units/ProductUnitTable' +import ProductIngredientTable from '../../../../../../../../views/apps/ecommerce/products/ingredient/ProductIngredientTable' const eCommerceProductsIngredient = () => { - return + return } export default eCommerceProductsIngredient diff --git a/src/app/[lang]/(dashboard)/(private)/apps/finance/payment-methods/list/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/finance/payment-methods/list/page.tsx new file mode 100644 index 0000000..d17cc33 --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/finance/payment-methods/list/page.tsx @@ -0,0 +1,25 @@ +import PaymentMethodListTable from '../../../../../../../../views/apps/finance/payment-methods/list/PaymentMethodListTable' + +/** + * ! If you need data using an API call, uncomment the below API code, update the `process.env.API_URL` variable in the + * ! `.env` file found at root of your project and also update the API endpoints like `/apps/ecommerce` in below example. + * ! Also, remove the above server action import and the action itself from the `src/app/server/actions.ts` file to clean up unused code + * ! because we've used the server action for getting our static data. + */ + +/* const getEcommerceData = async () => { + // Vars + const res = await fetch(`${process.env.API_URL}/apps/ecommerce`) + + if (!res.ok) { + throw new Error('Failed to fetch ecommerce data') + } + + return res.json() +} */ + +const PaymentMethodListTablePage = async () => { + return +} + +export default PaymentMethodListTablePage diff --git a/src/app/[lang]/(dashboard)/(private)/apps/organization/outlets/list/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/organization/outlets/list/page.tsx new file mode 100644 index 0000000..c79fa9e --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/organization/outlets/list/page.tsx @@ -0,0 +1,26 @@ + +/** + * ! If you need data using an API call, uncomment the below API code, update the `process.env.API_URL` variable in the + * ! `.env` file found at root of your project and also update the API endpoints like `/apps/ecommerce` in below example. + * ! Also, remove the above server action import and the action itself from the `src/app/server/actions.ts` file to clean up unused code + * ! because we've used the server action for getting our static data. + */ + +import OrganizationOutletListTable from "../../../../../../../../views/apps/organization/outlets/list/OrganizationOutletListTable" + +/* const getEcommerceData = async () => { + // Vars + const res = await fetch(`${process.env.API_URL}/apps/ecommerce`) + + if (!res.ok) { + throw new Error('Failed to fetch ecommerce data') + } + + return res.json() +} */ + +const OutletListTablePage = async () => { + return +} + +export default OutletListTablePage diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx index c5a9328..34f3b8a 100644 --- a/src/components/layout/vertical/VerticalMenu.tsx +++ b/src/components/layout/vertical/VerticalMenu.tsx @@ -90,6 +90,8 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => { {dictionary['navigation'].dashboard} {dictionary['navigation'].list} + {dictionary['navigation'].details} + {dictionary['navigation'].edit} {dictionary['navigation'].add} {dictionary['navigation'].category} @@ -105,15 +107,21 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => { {dictionary['navigation'].list} - {/* - {dictionary['navigation'].manageReviews} - - {dictionary['navigation'].referrals} */} + + {dictionary['navigation'].list} + {dictionary['navigation'].addjustment} + {dictionary['navigation'].settings} - }> - {dictionary['navigation'].list} - {dictionary['navigation'].addjustment} + }> + + {dictionary['navigation'].list} + + + }> + + {dictionary['navigation'].list} + }> {dictionary['navigation'].list} diff --git a/src/data/dictionaries/ar.json b/src/data/dictionaries/ar.json index c0843cc..a9c0fbf 100644 --- a/src/data/dictionaries/ar.json +++ b/src/data/dictionaries/ar.json @@ -21,6 +21,10 @@ "add": "يضيف", "addjustment": "تعديل", "category": "فئة", + "finance": "مالية", + "paymentMethods": "طرق الدفع", + "organization": "المنظمة", + "outlet": "مخزن", "units": "وحدات", "ingredients": "مكونات", "orders": "أوامر", diff --git a/src/data/dictionaries/en.json b/src/data/dictionaries/en.json index 09c7e45..4bb761b 100644 --- a/src/data/dictionaries/en.json +++ b/src/data/dictionaries/en.json @@ -22,6 +22,10 @@ "addjustment": "Addjustment", "category": "Category", "units": "Units", + "finance": "Finance", + "paymentMethods": "Payment Methods", + "organization": "Organization", + "outlet": "Outlet", "ingredients": "Ingredients", "orders": "Orders", "details": "Details", diff --git a/src/data/dictionaries/fr.json b/src/data/dictionaries/fr.json index 9753464..369c180 100644 --- a/src/data/dictionaries/fr.json +++ b/src/data/dictionaries/fr.json @@ -21,6 +21,10 @@ "add": "Ajouter", "addjustment": "Ajustement", "category": "Catégorie", + "finance": "Finance", + "paymentMethods": "Méthodes de paiement", + "organization": "Organisation", + "outlet": "Point de vente", "units": "Unites", "ingredients": "Ingrédients", "orders": "Ordres", diff --git a/src/redux-store/index.ts b/src/redux-store/index.ts index 72bab46..7981376 100644 --- a/src/redux-store/index.ts +++ b/src/redux-store/index.ts @@ -3,11 +3,13 @@ import { configureStore } from '@reduxjs/toolkit' import productReducer from '@/redux-store/slices/product' import customerReducer from '@/redux-store/slices/customer' +import paymentMethodReducer from '@/redux-store/slices/paymentMethod' export const store = configureStore({ reducer: { productReducer, - customerReducer + customerReducer, + paymentMethodReducer }, middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false }) }) diff --git a/src/redux-store/slices/paymentMethod.ts b/src/redux-store/slices/paymentMethod.ts new file mode 100644 index 0000000..0a2151b --- /dev/null +++ b/src/redux-store/slices/paymentMethod.ts @@ -0,0 +1,37 @@ +// Third-party Imports +import type { PayloadAction } from '@reduxjs/toolkit' +import { createSlice } from '@reduxjs/toolkit' + +// Type Imports + +// Data Imports +import { PaymentMethod } from '../../types/services/paymentMethod' + +const initialState: { currentPaymentMethod: PaymentMethod } = { + currentPaymentMethod: { + id: '', + organization_id: '', + name: '', + type: '', + is_active: true, + created_at: '', + updated_at: '' + } +} + +export const paymentMethodSlice = createSlice({ + name: 'paymentMethod', + initialState, + reducers: { + setPaymentMethod: (state, action: PayloadAction) => { + state.currentPaymentMethod = action.payload + }, + resetPaymentMethod: state => { + state.currentPaymentMethod = initialState.currentPaymentMethod + } + } +}) + +export const { setPaymentMethod, resetPaymentMethod } = paymentMethodSlice.actions + +export default paymentMethodSlice.reducer diff --git a/src/services/mutations/paymentMethods.ts b/src/services/mutations/paymentMethods.ts new file mode 100644 index 0000000..b7695bd --- /dev/null +++ b/src/services/mutations/paymentMethods.ts @@ -0,0 +1,52 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { api } from '../api' +import { toast } from 'react-toastify' +import { PaymentMethodRequest } from '../../types/services/paymentMethod' + +export const usePaymentMethodsMutation = () => { + const queryClient = useQueryClient() + + const createPaymentMethod = useMutation({ + mutationFn: async (newPaymentMethod: PaymentMethodRequest) => { + const response = await api.post('/payment-methods', newPaymentMethod) + return response.data + }, + onSuccess: () => { + toast.success('PaymentMethod created successfully!') + queryClient.invalidateQueries({ queryKey: ['payment-methods'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') + } + }) + + const updatePaymentMethod = useMutation({ + mutationFn: async ({ id, payload }: { id: string; payload: PaymentMethodRequest }) => { + const response = await api.put(`/payment-methods/${id}`, payload) + return response.data + }, + onSuccess: () => { + toast.success('PaymentMethod updated successfully!') + queryClient.invalidateQueries({ queryKey: ['payment-methods'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed') + } + }) + + const deletePaymentMethod = useMutation({ + mutationFn: async (id: string) => { + const response = await api.delete(`/payment-methods/${id}`) + return response.data + }, + onSuccess: () => { + toast.success('PaymentMethod deleted successfully!') + queryClient.invalidateQueries({ queryKey: ['payment-methods'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed') + } + }) + + return { createPaymentMethod, updatePaymentMethod, deletePaymentMethod } +} diff --git a/src/services/mutations/products.ts b/src/services/mutations/products.ts index 3cd8c72..08cd2dc 100644 --- a/src/services/mutations/products.ts +++ b/src/services/mutations/products.ts @@ -13,6 +13,7 @@ export const useProductsMutation = () => { }, onSuccess: () => { toast.success('Product created successfully!') + queryClient.invalidateQueries({ queryKey: ['products'] }) }, onError: (error: any) => { toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') @@ -26,6 +27,7 @@ export const useProductsMutation = () => { }, onSuccess: () => { toast.success('Product updated successfully!') + queryClient.invalidateQueries({ queryKey: ['products'] }) }, onError: (error: any) => { toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed') diff --git a/src/services/queries/paymentMethods.ts b/src/services/queries/paymentMethods.ts new file mode 100644 index 0000000..2002b9f --- /dev/null +++ b/src/services/queries/paymentMethods.ts @@ -0,0 +1,36 @@ +import { useQuery } from '@tanstack/react-query' +import { PaymentMethods } from '../../types/services/paymentMethod' +import { api } from '../api' + +interface PaymentMethodsQueryParams { + page?: number + limit?: number + search?: string +} + +export function usePaymentMethods(params: PaymentMethodsQueryParams = {}) { + const { page = 1, limit = 10, search = '', ...filters } = params + + return useQuery({ + queryKey: ['payment-methods', { page, limit, search, ...filters }], + queryFn: async () => { + const queryParams = new URLSearchParams() + + queryParams.append('page', page.toString()) + queryParams.append('limit', limit.toString()) + + if (search) { + queryParams.append('search', search) + } + + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/payment-methods?${queryParams.toString()}`) + return res.data.data + } + }) +} diff --git a/src/types/services/order.ts b/src/types/services/order.ts index 32d2755..ede701e 100644 --- a/src/types/services/order.ts +++ b/src/types/services/order.ts @@ -12,8 +12,8 @@ export interface Order { outlet_id: string user_id: string table_number: string - order_type: 'dineIn' | 'takeAway' | 'delivery' - status: 'pending' | 'inProgress' | 'completed' | 'cancelled' + order_type: string + status: string subtotal: number tax_amount: number discount_amount: number @@ -39,7 +39,7 @@ export interface OrderItem { total_price: number modifiers: any[] notes: string - status: 'pending' | 'completed' | 'cancelled' + status: string created_at: string updated_at: string } diff --git a/src/types/services/paymentMethod.ts b/src/types/services/paymentMethod.ts index 6cdf6dd..b3f6000 100644 --- a/src/types/services/paymentMethod.ts +++ b/src/types/services/paymentMethod.ts @@ -10,10 +10,14 @@ export interface PaymentMethod { id: string; organization_id: string; name: string; - type: PaymentMethodType; + type: string; is_active: boolean; created_at: string; // ISO 8601 timestamp updated_at: string; // ISO 8601 timestamp } -export type PaymentMethodType = "cash" | "card" | "edc" | "delivery" | string; +export interface PaymentMethodRequest { + name: string; + type: string; + is_active: boolean; +} diff --git a/src/utils/transform.ts b/src/utils/transform.ts new file mode 100644 index 0000000..719b3b9 --- /dev/null +++ b/src/utils/transform.ts @@ -0,0 +1,17 @@ +export const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(amount) +} + +export const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) +} diff --git a/src/views/apps/ecommerce/customers/list/AddCustomerDrawer.tsx b/src/views/apps/ecommerce/customers/list/AddCustomerDrawer.tsx index 0610a13..8ea2caa 100644 --- a/src/views/apps/ecommerce/customers/list/AddCustomerDrawer.tsx +++ b/src/views/apps/ecommerce/customers/list/AddCustomerDrawer.tsx @@ -1,117 +1,93 @@ // React Imports -import { useState } from 'react' +import { use, useEffect, useState } from 'react' // MUI Imports import Button from '@mui/material/Button' -import Drawer from '@mui/material/Drawer' import Divider from '@mui/material/Divider' +import Drawer from '@mui/material/Drawer' import IconButton from '@mui/material/IconButton' -import MenuItem from '@mui/material/MenuItem' import Switch from '@mui/material/Switch' import Typography from '@mui/material/Typography' // Third-party Imports import PerfectScrollbar from 'react-perfect-scrollbar' -import { useForm, Controller } from 'react-hook-form' // Type Imports -import type { Customer } from '@/types/apps/ecommerceTypes' // Component Imports import CustomTextField from '@core/components/mui/TextField' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../../../../../redux-store' +import { useCustomersMutation } from '../../../../../services/mutations/customers' +import { CustomerRequest } from '../../../../../types/services/customer' +import { resetCustomer } from '../../../../../redux-store/slices/customer' type Props = { open: boolean handleClose: () => void - setData: (data: Customer[]) => void - customerData?: Customer[] -} - -type FormValidateType = { - fullName: string - email: string - country: string -} - -type FormNonValidateType = { - contact: string - address1: string - address2: string - town: string - state: string - postcode: string -} - -type countryType = { - country: string -} - -export const country: { [key: string]: countryType } = { - india: { country: 'India' }, - australia: { country: 'Australia' }, - france: { country: 'France' }, - brazil: { country: 'Brazil' }, - us: { country: 'United States' }, - china: { country: 'China' } } // Vars const initialData = { - contact: '', - address1: '', - address2: '', - town: '', - state: '', - postcode: '' + name: '', + email: '', + phone: '', + address: '', + is_active: true } const AddCustomerDrawer = (props: Props) => { + const dispatch = useDispatch() + // Props - const { open, handleClose, setData, customerData } = props + const { open, handleClose } = props + + const { createCustomer, updateCustomer } = useCustomersMutation() + const { currentCustomer } = useSelector((state: RootState) => state.customerReducer) // States - const [formData, setFormData] = useState(initialData) + const [formData, setFormData] = useState(initialData) - // Hooks - const { - control, - reset: resetForm, - handleSubmit, - formState: { errors } - } = useForm({ - defaultValues: { - fullName: '', - email: '', - country: '' + useEffect(() => { + if (currentCustomer.id) { + setFormData(currentCustomer) } - }) + }, [currentCustomer]) - const onSubmit = (data: FormValidateType) => { - const newData: Customer = { - id: (customerData?.length && customerData?.length + 1) || 1, - customer: data.fullName, - customerId: customerData?.[Math.floor(Math.random() * 100) + 1].customerId ?? '1', - email: data.email, - country: `${country[data.country].country}`, - countryCode: 'st', - countryFlag: `/images/cards/${data.country}.png`, - order: Math.floor(Math.random() * 1000) + 1, - totalSpent: Math.floor(Math.random() * (1000000 - 100) + 100) / 100, - avatar: `/images/avatars/${Math.floor(Math.random() * 8) + 1}.png` + const handleSubmit = (e: any) => { + e.preventDefault() + + if (currentCustomer.id) { + updateCustomer.mutate( + { id: currentCustomer.id, payload: formData }, + { + onSuccess: () => { + handleReset() + } + } + ) + } else { + createCustomer.mutate(formData, { + onSuccess: () => { + handleReset() + } + }) } - - setData([...(customerData ?? []), newData]) - resetForm({ fullName: '', email: '', country: '' }) - setFormData(initialData) - handleClose() } const handleReset = () => { handleClose() - resetForm({ fullName: '', email: '', country: '' }) + dispatch(resetCustomer()) setFormData(initialData) } + const handleInputChange = (e: any) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }) + } + return ( { sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }} >
- Add a Customer + {currentCustomer.id ? 'Edit' : 'Add'} Customer @@ -130,60 +106,26 @@ const AddCustomerDrawer = (props: Props) => {
-
onSubmit(data))} className='flex flex-col gap-5'> + Basic Information - ( - - )} + - ( - - )} - /> - ( - - India - Australia - France - Brazil - USA - China - - )} + placeholder='johndoe@email' + value={formData.email} + onChange={handleInputChange} /> Shipping Information @@ -191,63 +133,41 @@ const AddCustomerDrawer = (props: Props) => { setFormData({ ...formData, address1: e.target.value })} - /> - setFormData({ ...formData, address2: e.target.value })} - /> - setFormData({ ...formData, town: e.target.value })} - /> - setFormData({ ...formData, state: e.target.value })} - /> - setFormData({ ...formData, postcode: e.target.value })} + value={formData.address} + onChange={handleInputChange} /> setFormData({ ...formData, contact: e.target.value })} + name='phone' + value={formData.phone} + onChange={handleInputChange} /> -
+
- Use as a billing address? + Active - Please check budget for more info.
- + setFormData({ ...formData, is_active: e.target.checked })} + />
- +
+
+
+ {isLoading ? ( + + ) : ( + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + {table.getFilteredRowModel().rows.length === 0 ? ( + + + + + + ) : ( + + {table + .getRowModel() + .rows.slice(0, table.getState().pagination.pageSize) + .map(row => { + return ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ) + })} + + )} +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: + }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} +
+ + )} +
+ No data available +
{flexRender(cell.column.columnDef.cell, cell.getContext())}
+ )} + + {isFetching && !isLoading && ( + + + + )} +
+ ( + + )} + count={totalCount} + rowsPerPage={pageSize} + page={currentPage} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + disabled={isLoading} + /> + + + {/* setAddUnitOpen(!addUnitOpen)} /> + + setEditUnitOpen(!editUnitOpen)} data={currentUnit!} /> */} + + setOpenConfirm(false)} + onConfirm={handleDelete} + isLoading={isDeleting} + title='Delete Unit' + message='Are you sure you want to delete this Unit? This action cannot be undone.' + /> + + ) +} + +export default ProductIngredientTable diff --git a/src/views/apps/ecommerce/products/list/ProductListTable.tsx b/src/views/apps/ecommerce/products/list/ProductListTable.tsx index 84d8571..cb8745e 100644 --- a/src/views/apps/ecommerce/products/list/ProductListTable.tsx +++ b/src/views/apps/ecommerce/products/list/ProductListTable.tsx @@ -47,6 +47,7 @@ import { useProducts } from '../../../../../services/queries/products' import { Product } from '../../../../../types/services/product' import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' import { useProductsMutation } from '../../../../../services/mutations/products' +import { formatCurrency } from '../../../../../utils/transform' declare module '@tanstack/table-core' { interface FilterFns { @@ -112,6 +113,7 @@ const ProductListTable = () => { const [pageSize, setPageSize] = useState(10) const [openConfirm, setOpenConfirm] = useState(false) const [productId, setProductId] = useState('') + const [search, setSearch] = useState('') // Hooks const { lang: locale } = useParams() @@ -119,7 +121,8 @@ const ProductListTable = () => { // Fetch products with pagination and search const { data, isLoading, error, isFetching } = useProducts({ page: currentPage, - limit: pageSize + limit: pageSize, + search }) const { mutate: deleteProduct, isPending: isDeleting } = useProductsMutation().deleteProduct @@ -135,7 +138,7 @@ const ProductListTable = () => { const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { const newPageSize = parseInt(event.target.value, 10) setPageSize(newPageSize) - setCurrentPage(0) // Reset to first page + setCurrentPage(1) // Reset to first page }, []) const handleDelete = () => { @@ -182,33 +185,17 @@ const ProductListTable = () => {
) }), - // columnHelper.accessor('category_id', { - // header: 'Category', - // cell: ({ row }) => ( - //
- // - // - // - // {row.original.category_id || '-'} - //
- // ) - // }), - // columnHelper.accessor('stock', { - // header: 'Stock', - // cell: ({ row }) => , - // enableSorting: false - // }), columnHelper.accessor('sku', { header: 'SKU', cell: ({ row }) => {row.original.sku} }), columnHelper.accessor('price', { header: 'Price', - cell: ({ row }) => {row.original.price} + cell: ({ row }) => {formatCurrency(row.original.price)} }), columnHelper.accessor('cost', { header: 'Cost', - cell: ({ row }) => {row.original.cost} + cell: ({ row }) => {formatCurrency(row.original.cost)} }), columnHelper.accessor('is_active', { header: 'Status', @@ -273,7 +260,7 @@ const ProductListTable = () => { state: { rowSelection, pagination: { - pageIndex: currentPage, // <= penting! + pageIndex: currentPage, pageSize } }, @@ -293,8 +280,8 @@ const ProductListTable = () => {
console.log(value)} + value={search} + onChange={value => setSearch(value as string)} placeholder='Search Product' className='max-sm:is-full' /> diff --git a/src/views/apps/ecommerce/products/units/ProductUnitTable.tsx b/src/views/apps/ecommerce/products/units/ProductUnitTable.tsx index aa6c875..cffc85c 100644 --- a/src/views/apps/ecommerce/products/units/ProductUnitTable.tsx +++ b/src/views/apps/ecommerce/products/units/ProductUnitTable.tsx @@ -27,7 +27,7 @@ import OptionMenu from '@core/components/option-menu' // Style Imports import tableStyles from '@core/styles/table.module.css' -import { Box, CircularProgress } from '@mui/material' +import { Box, Chip, CircularProgress } from '@mui/material' import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' import Loading from '../../../../../components/layout/shared/Loading' import { useUnitsMutation } from '../../../../../services/mutations/units' @@ -35,6 +35,7 @@ import { useUnits } from '../../../../../services/queries/units' import { Unit } from '../../../../../types/services/unit' import AddUnitDrawer from './AddUnitDrawer' import EditUnitDrawer from './EditUnitDrawer' +import { formatDate } from '../../../../../utils/transform' declare module '@tanstack/table-core' { interface FilterFns { @@ -124,7 +125,7 @@ const ProductUnitTable = () => { const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { const newPageSize = parseInt(event.target.value, 10) setPageSize(newPageSize) - setCurrentPage(0) // Reset to first page + setCurrentPage(1) // Reset to first page }, []) const handleDelete = () => { @@ -176,11 +177,18 @@ const ProductUnitTable = () => { }), columnHelper.accessor('is_active', { header: 'Status', - cell: ({ row }) => {row.original.is_active ? 'Active' : 'Inactive'} + cell: ({ row }) => ( + + ) }), columnHelper.accessor('created_at', { header: 'Created Date', - cell: ({ row }) => {row.original.created_at} + cell: ({ row }) => {formatDate(row.original.created_at)} }), columnHelper.accessor('actions', { header: 'Actions', diff --git a/src/views/apps/stock/adjustment/AdjustmentStockDrawer.tsx b/src/views/apps/ecommerce/stock/adjustment/AdjustmentStockDrawer.tsx similarity index 94% rename from src/views/apps/stock/adjustment/AdjustmentStockDrawer.tsx rename to src/views/apps/ecommerce/stock/adjustment/AdjustmentStockDrawer.tsx index 06932c4..f3e7a46 100644 --- a/src/views/apps/stock/adjustment/AdjustmentStockDrawer.tsx +++ b/src/views/apps/ecommerce/stock/adjustment/AdjustmentStockDrawer.tsx @@ -16,10 +16,10 @@ import Typography from '@mui/material/Typography' import CustomTextField from '@core/components/mui/TextField' import { Autocomplete, CircularProgress } from '@mui/material' import { useDebounce } from 'use-debounce' -import { useInventoriesMutation } from '../../../../services/mutations/inventories' -import { useOutlets } from '../../../../services/queries/outlets' -import { useProducts } from '../../../../services/queries/products' -import { InventoryAdjustRequest } from '../../../../types/services/inventory' +import { useInventoriesMutation } from '../../../../../services/mutations/inventories' +import { useOutlets } from '../../../../../services/queries/outlets' +import { useProducts } from '../../../../../services/queries/products' +import { InventoryAdjustRequest } from '../../../../../types/services/inventory' type Props = { open: boolean diff --git a/src/views/apps/stock/adjustment/StockListTable.tsx b/src/views/apps/ecommerce/stock/adjustment/StockListTable.tsx similarity index 97% rename from src/views/apps/stock/adjustment/StockListTable.tsx rename to src/views/apps/ecommerce/stock/adjustment/StockListTable.tsx index 2cc82b5..ca4e04f 100644 --- a/src/views/apps/stock/adjustment/StockListTable.tsx +++ b/src/views/apps/ecommerce/stock/adjustment/StockListTable.tsx @@ -35,10 +35,10 @@ import CustomTextField from '@core/components/mui/TextField' // Style Imports import tableStyles from '@core/styles/table.module.css' import { Box, CircularProgress } from '@mui/material' -import Loading from '../../../../components/layout/shared/Loading' -import { useInventories } from '../../../../services/queries/inventories' -import { Inventory } from '../../../../types/services/inventory' import AdjustmentStockDrawer from './AdjustmentStockDrawer' +import { Inventory } from '../../../../../types/services/inventory' +import Loading from '../../../../../components/layout/shared/Loading' +import { useInventories } from '../../../../../services/queries/inventories' declare module '@tanstack/table-core' { interface FilterFns { @@ -121,7 +121,7 @@ const StockListTable = () => { const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { const newPageSize = parseInt(event.target.value, 10) setPageSize(newPageSize) - setCurrentPage(0) // Reset to first page + setCurrentPage(1) // Reset to first page }, []) const columns = useMemo[]>( diff --git a/src/views/apps/stock/list/AddStockDrawer.tsx b/src/views/apps/ecommerce/stock/list/AddStockDrawer.tsx similarity index 94% rename from src/views/apps/stock/list/AddStockDrawer.tsx rename to src/views/apps/ecommerce/stock/list/AddStockDrawer.tsx index 8609af1..44e37f2 100644 --- a/src/views/apps/stock/list/AddStockDrawer.tsx +++ b/src/views/apps/ecommerce/stock/list/AddStockDrawer.tsx @@ -16,10 +16,10 @@ import Typography from '@mui/material/Typography' import CustomTextField from '@core/components/mui/TextField' import { Autocomplete, CircularProgress } from '@mui/material' import { useDebounce } from 'use-debounce' -import { useInventoriesMutation } from '../../../../services/mutations/inventories' -import { useOutlets } from '../../../../services/queries/outlets' -import { useProducts } from '../../../../services/queries/products' -import { InventoryRequest } from '../../../../types/services/inventory' +import { useInventoriesMutation } from '../../../../../services/mutations/inventories' +import { useOutlets } from '../../../../../services/queries/outlets' +import { useProducts } from '../../../../../services/queries/products' +import { InventoryRequest } from '../../../../../types/services/inventory' type Props = { open: boolean diff --git a/src/views/apps/stock/list/StockListTable.tsx b/src/views/apps/ecommerce/stock/list/StockListTable.tsx similarity index 95% rename from src/views/apps/stock/list/StockListTable.tsx rename to src/views/apps/ecommerce/stock/list/StockListTable.tsx index 3372590..d56eaac 100644 --- a/src/views/apps/stock/list/StockListTable.tsx +++ b/src/views/apps/ecommerce/stock/list/StockListTable.tsx @@ -36,12 +36,12 @@ import OptionMenu from '@core/components/option-menu' // Style Imports import tableStyles from '@core/styles/table.module.css' import { Box, CircularProgress } from '@mui/material' -import ConfirmDeleteDialog from '../../../../components/dialogs/confirm-delete' -import Loading from '../../../../components/layout/shared/Loading' -import { useInventoriesMutation } from '../../../../services/mutations/inventories' -import { useInventories } from '../../../../services/queries/inventories' -import { Inventory } from '../../../../types/services/inventory' import AddStockDrawer from './AddStockDrawer' +import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' +import Loading from '../../../../../components/layout/shared/Loading' +import { useInventoriesMutation } from '../../../../../services/mutations/inventories' +import { useInventories } from '../../../../../services/queries/inventories' +import { Inventory } from '../../../../../types/services/inventory' declare module '@tanstack/table-core' { interface FilterFns { @@ -128,7 +128,7 @@ const StockListTable = () => { const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { const newPageSize = parseInt(event.target.value, 10) setPageSize(newPageSize) - setCurrentPage(0) // Reset to first page + setCurrentPage(1) // Reset to first page }, []) const handleDelete = () => { @@ -252,8 +252,8 @@ const StockListTable = () => {
table.setPageSize(Number(e.target.value))} + value={pageSize} + onChange={handlePageSizeChange} className='flex-auto is-[70px] max-sm:is-full' > 10 diff --git a/src/views/apps/stock/list/TableFilters.tsx b/src/views/apps/ecommerce/stock/list/TableFilters.tsx similarity index 95% rename from src/views/apps/stock/list/TableFilters.tsx rename to src/views/apps/ecommerce/stock/list/TableFilters.tsx index fec459d..cc1dccf 100644 --- a/src/views/apps/stock/list/TableFilters.tsx +++ b/src/views/apps/ecommerce/stock/list/TableFilters.tsx @@ -1,17 +1,16 @@ // React Imports -import { useState, useEffect } from 'react' +import { useEffect, useState } from 'react' // MUI Imports -import Grid from '@mui/material/Grid2' import CardContent from '@mui/material/CardContent' +import Grid from '@mui/material/Grid2' import MenuItem from '@mui/material/MenuItem' // Type Imports -import type { ProductType } from '@/types/apps/ecommerceTypes' // Component Imports import CustomTextField from '@core/components/mui/TextField' -import { Product } from '../../../../types/services/product' +import { Product } from '../../../../../types/services/product' type ProductStockType = { [key: string]: boolean } diff --git a/src/views/apps/finance/payment-methods/list/AddPaymentMethodDrawer.tsx b/src/views/apps/finance/payment-methods/list/AddPaymentMethodDrawer.tsx new file mode 100644 index 0000000..4e9675a --- /dev/null +++ b/src/views/apps/finance/payment-methods/list/AddPaymentMethodDrawer.tsx @@ -0,0 +1,167 @@ +// React Imports +import { useEffect, useState } from 'react' + +// MUI Imports +import Button from '@mui/material/Button' +import Divider from '@mui/material/Divider' +import Drawer from '@mui/material/Drawer' +import IconButton from '@mui/material/IconButton' +import Switch from '@mui/material/Switch' +import Typography from '@mui/material/Typography' + +// Third-party Imports +import PerfectScrollbar from 'react-perfect-scrollbar' + +// Type Imports + +// Component Imports +import CustomTextField from '@core/components/mui/TextField' +import { MenuItem } from '@mui/material' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../../../../../redux-store' +import { usePaymentMethodsMutation } from '../../../../../services/mutations/paymentMethods' +import { PaymentMethodRequest } from '../../../../../types/services/paymentMethod' +import { resetPaymentMethod } from '../../../../../redux-store/slices/paymentMethod' + +type Props = { + open: boolean + handleClose: () => void +} + +// Vars +const initialData = { + name: '', + type: '', + is_active: true +} + +const AddPaymentMethodDrawer = (props: Props) => { + const dispatch = useDispatch() + // Props + const { open, handleClose } = props + + const { createPaymentMethod, updatePaymentMethod } = usePaymentMethodsMutation() + const { currentPaymentMethod } = useSelector((state: RootState) => state.paymentMethodReducer) + + // States + const [formData, setFormData] = useState(initialData) + + useEffect(() => { + if (currentPaymentMethod.id) { + setFormData(currentPaymentMethod) + } + }, [currentPaymentMethod]) + + const handleSubmit = (e: any) => { + e.preventDefault() + + if (currentPaymentMethod.id) { + updatePaymentMethod.mutate( + { id: currentPaymentMethod.id, payload: formData }, + { + onSuccess: () => { + handleReset() + } + } + ) + } else { + createPaymentMethod.mutate(formData, { + onSuccess: () => { + handleReset() + } + }) + } + } + + const handleReset = () => { + handleClose() + dispatch(resetPaymentMethod()) + setFormData(initialData) + } + + const handleInputChange = (e: any) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }) + } + + return ( + +
+ {currentPaymentMethod.id ? 'Edit' : 'Add'} Payment Method + + + +
+ + +
+ + + Basic Information + + + setFormData({ ...formData, type: e.target.value })} + > + Cash + Card + Digital Wallet + +
+
+ + Active + +
+ setFormData({ ...formData, is_active: e.target.checked })} + /> +
+
+ + +
+ +
+
+
+ ) +} + +export default AddPaymentMethodDrawer diff --git a/src/views/apps/finance/payment-methods/list/PaymentMethodListTable.tsx b/src/views/apps/finance/payment-methods/list/PaymentMethodListTable.tsx new file mode 100644 index 0000000..3a14f61 --- /dev/null +++ b/src/views/apps/finance/payment-methods/list/PaymentMethodListTable.tsx @@ -0,0 +1,401 @@ +'use client' + +// React Imports +import { useCallback, useEffect, useMemo, useState } from 'react' + +// Next Imports + +// MUI Imports +import Button from '@mui/material/Button' +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import Checkbox from '@mui/material/Checkbox' +import MenuItem from '@mui/material/MenuItem' +import type { TextFieldProps } from '@mui/material/TextField' +import Typography from '@mui/material/Typography' + +// Third-party Imports +import type { RankingInfo } from '@tanstack/match-sorter-utils' +import { rankItem } from '@tanstack/match-sorter-utils' +import type { ColumnDef, FilterFn } from '@tanstack/react-table' +import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table' +import classnames from 'classnames' + +// Type Imports + +// Component Imports +import CustomTextField from '@core/components/mui/TextField' + +// Util Imports + +// Style Imports +import tableStyles from '@core/styles/table.module.css' +import { Box, Chip, CircularProgress, IconButton, TablePagination } from '@mui/material' +import { useDispatch } from 'react-redux' +import OptionMenu from '../../../../../@core/components/option-menu' +import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' +import Loading from '../../../../../components/layout/shared/Loading' +import TablePaginationComponent from '../../../../../components/TablePaginationComponent' +import { setPaymentMethod } from '../../../../../redux-store/slices/paymentMethod' +import { usePaymentMethodsMutation } from '../../../../../services/mutations/paymentMethods' +import { usePaymentMethods } from '../../../../../services/queries/paymentMethods' +import { PaymentMethod } from '../../../../../types/services/paymentMethod' +import AddPaymentMethodDrawer from './AddPaymentMethodDrawer' + +declare module '@tanstack/table-core' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank: RankingInfo + } +} + +type FinancePaymentMethodTypeWithAction = PaymentMethod & { + actions?: string +} + +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info + addMeta({ + itemRank + }) + + // Return if the item should be filtered in/out + return itemRank.passed +} + +const DebouncedInput = ({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit) => { + // States + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + useEffect(() => { + const timeout = setTimeout(() => { + onChange(value) + }, debounce) + + return () => clearTimeout(timeout) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + + return setValue(e.target.value)} /> +} + +// Column Definitions +const columnHelper = createColumnHelper() + +const PaymentMethodListTable = () => { + const dispatch = useDispatch() + + // States + const [paymentMethodOpen, setPaymentMethodOpen] = useState(false) + const [rowSelection, setRowSelection] = useState({}) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [openConfirm, setOpenConfirm] = useState(false) + const [paymentMethodId, setPaymentMethodId] = useState('') + const [search, setSearch] = useState('') + + const { data, isLoading, error, isFetching } = usePaymentMethods({ + page: currentPage, + limit: pageSize, + search + }) + + const { deletePaymentMethod } = usePaymentMethodsMutation() + + const paymentMethods = data?.payment_methods ?? [] + const totalCount = data?.total_count ?? 0 + + const handlePageChange = useCallback((event: unknown, newPage: number) => { + setCurrentPage(newPage) + }, []) + + // Handle page size change + const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { + const newPageSize = parseInt(event.target.value, 10) + setPageSize(newPageSize) + setCurrentPage(1) // Reset to first page + }, []) + + const handleDelete = () => { + deletePaymentMethod.mutate(paymentMethodId, { + onSuccess: () => setOpenConfirm(false) + }) + } + + const columns = useMemo[]>( + () => [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ) + }, + columnHelper.accessor('name', { + header: 'Name', + cell: ({ row }) => {row.original.name || '-'} + }), + columnHelper.accessor('type', { + header: 'Type', + cell: ({ row }) => {row.original.type || '-'} + }), + columnHelper.accessor('is_active', { + header: 'Status', + cell: ({ row }) => ( + + ) + }), + columnHelper.accessor('created_at', { + header: 'Created Date', + cell: ({ row }) => {row.original.created_at || '-'} + }), + columnHelper.accessor('actions', { + header: 'Actions', + cell: ({ row }) => ( +
+ { + dispatch(setPaymentMethod(row.original)) + setPaymentMethodOpen(true) + }} + > + + + { + setOpenConfirm(true) + setPaymentMethodId(row.original.id) + } + } + }, + { text: 'Duplicate', icon: 'tabler-copy' } + ]} + /> +
+ ), + enableSorting: false + }) + ], + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) + + const table = useReactTable({ + data: paymentMethods as PaymentMethod[], + columns, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + rowSelection, + pagination: { + pageIndex: currentPage, + pageSize + } + }, + enableRowSelection: true, //enable row selection for all rows + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + // Disable client-side pagination since we're handling it server-side + manualPagination: true, + pageCount: Math.ceil(totalCount / pageSize) + }) + + return ( + <> + + + setSearch(value as string)} + placeholder='Search' + className='max-sm:is-full' + /> +
+ + 10 + 25 + 50 + 100 + + + +
+
+
+ {isLoading ? ( + + ) : ( + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + {table.getFilteredRowModel().rows.length === 0 ? ( + + + + + + ) : ( + + {table + .getRowModel() + .rows.slice(0, table.getState().pagination.pageSize) + .map(row => { + return ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ) + })} + + )} +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: + }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} +
+ + )} +
+ No data available +
{flexRender(cell.column.columnDef.cell, cell.getContext())}
+ )} + + {isFetching && !isLoading && ( + + + + )} +
+ + ( + + )} + count={totalCount} + rowsPerPage={pageSize} + page={currentPage} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + disabled={isLoading} + /> +
+ + setPaymentMethodOpen(!paymentMethodOpen)} /> + + setOpenConfirm(false)} + onConfirm={handleDelete} + isLoading={deletePaymentMethod.isPending} + title='Delete paymentMethod' + message='Are you sure you want to delete this paymentMethod? This action cannot be undone.' + /> + + ) +} + +export default PaymentMethodListTable diff --git a/src/views/apps/organization/outlets/list/OrganizationOutletListTable.tsx b/src/views/apps/organization/outlets/list/OrganizationOutletListTable.tsx new file mode 100644 index 0000000..c7f5031 --- /dev/null +++ b/src/views/apps/organization/outlets/list/OrganizationOutletListTable.tsx @@ -0,0 +1,395 @@ +'use client' + +// React Imports +import { useCallback, useEffect, useMemo, useState } from 'react' + +// Next Imports + +// MUI Imports +import Button from '@mui/material/Button' +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import Checkbox from '@mui/material/Checkbox' +import MenuItem from '@mui/material/MenuItem' +import type { TextFieldProps } from '@mui/material/TextField' +import Typography from '@mui/material/Typography' + +// Third-party Imports +import type { RankingInfo } from '@tanstack/match-sorter-utils' +import { rankItem } from '@tanstack/match-sorter-utils' +import type { ColumnDef, FilterFn } from '@tanstack/react-table' +import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table' +import classnames from 'classnames' + +// Type Imports + +// Component Imports +import CustomTextField from '@core/components/mui/TextField' + +// Util Imports + +// Style Imports +import tableStyles from '@core/styles/table.module.css' +import { Box, Chip, CircularProgress, IconButton, TablePagination } from '@mui/material' +import { useDispatch } from 'react-redux' +import OptionMenu from '../../../../../@core/components/option-menu' +import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' +import Loading from '../../../../../components/layout/shared/Loading' +import TablePaginationComponent from '../../../../../components/TablePaginationComponent' +import { usePaymentMethodsMutation } from '../../../../../services/mutations/paymentMethods' +import { useOutlets } from '../../../../../services/queries/outlets' +import { Outlet } from '../../../../../types/services/outlet' + +declare module '@tanstack/table-core' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank: RankingInfo + } +} + +type OrganizationOutletTypeWithAction = Outlet & { + actions?: string +} + +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info + addMeta({ + itemRank + }) + + // Return if the item should be filtered in/out + return itemRank.passed +} + +const DebouncedInput = ({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit) => { + // States + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + useEffect(() => { + const timeout = setTimeout(() => { + onChange(value) + }, debounce) + + return () => clearTimeout(timeout) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + + return setValue(e.target.value)} /> +} + +// Column Definitions +const columnHelper = createColumnHelper() + +const OrganizationOutletListTable = () => { + const dispatch = useDispatch() + + // States + const [paymentMethodOpen, setPaymentMethodOpen] = useState(false) + const [rowSelection, setRowSelection] = useState({}) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [openConfirm, setOpenConfirm] = useState(false) + const [paymentMethodId, setPaymentMethodId] = useState('') + const [search, setSearch] = useState('') + + const { data, isLoading, error, isFetching } = useOutlets({ + page: currentPage, + limit: pageSize, + search + }) + + const { deletePaymentMethod } = usePaymentMethodsMutation() + + const outlets = data?.outlets ?? [] + const totalCount = data?.total_count ?? 0 + + const handlePageChange = useCallback((event: unknown, newPage: number) => { + setCurrentPage(newPage) + }, []) + + // Handle page size change + const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { + const newPageSize = parseInt(event.target.value, 10) + setPageSize(newPageSize) + setCurrentPage(1) // Reset to first page + }, []) + + const handleDelete = () => { + deletePaymentMethod.mutate(paymentMethodId, { + onSuccess: () => setOpenConfirm(false) + }) + } + + const columns = useMemo[]>( + () => [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ) + }, + columnHelper.accessor('name', { + header: 'Name', + cell: ({ row }) => {row.original.name || '-'} + }), + columnHelper.accessor('address', { + header: 'Address', + cell: ({ row }) => {row.original.address || '-'} + }), + columnHelper.accessor('phone_number', { + header: 'Phone', + cell: ({ row }) => {row.original.phone_number || '-'} + }), + columnHelper.accessor('business_type', { + header: 'Business', + cell: ({ row }) => {row.original.business_type || '-'} + }), + columnHelper.accessor('is_active', { + header: 'Status', + cell: ({ row }) => ( + + ) + }), + columnHelper.accessor('currency', { + header: 'Currency', + cell: ({ row }) => {row.original.currency || '-'} + }), + columnHelper.accessor('tax_rate', { + header: 'Tax', + cell: ({ row }) => {row.original.tax_rate || '-'} + }), + columnHelper.accessor('actions', { + header: 'Actions', + cell: ({ row }) => ( +
+ + + + { + setOpenConfirm(true) + setPaymentMethodId(row.original.id) + } + } + }, + { text: 'Duplicate', icon: 'tabler-copy' } + ]} + /> +
+ ), + enableSorting: false + }) + ], + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) + + const table = useReactTable({ + data: outlets as Outlet[], + columns, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + rowSelection, + pagination: { + pageIndex: currentPage, + pageSize + } + }, + enableRowSelection: true, //enable row selection for all rows + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + // Disable client-side pagination since we're handling it server-side + manualPagination: true, + pageCount: Math.ceil(totalCount / pageSize) + }) + + return ( + <> + + + setSearch(value as string)} + placeholder='Search' + className='max-sm:is-full' + /> +
+ + 10 + 25 + 50 + 100 + + +
+
+
+ {isLoading ? ( + + ) : ( + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + {table.getFilteredRowModel().rows.length === 0 ? ( + + + + + + ) : ( + + {table + .getRowModel() + .rows.slice(0, table.getState().pagination.pageSize) + .map(row => { + return ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ) + })} + + )} +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: + }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} +
+ + )} +
+ No data available +
{flexRender(cell.column.columnDef.cell, cell.getContext())}
+ )} + + {isFetching && !isLoading && ( + + + + )} +
+ + ( + + )} + count={totalCount} + rowsPerPage={pageSize} + page={currentPage} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + disabled={isLoading} + /> +
+ + setOpenConfirm(false)} + onConfirm={handleDelete} + isLoading={deletePaymentMethod.isPending} + title='Delete paymentMethod' + message='Are you sure you want to delete this paymentMethod? This action cannot be undone.' + /> + + ) +} + +export default OrganizationOutletListTable