From 0906188c126ae651ee8b2983d1a5c762123c786e Mon Sep 17 00:00:00 2001 From: ferdiansyah783 Date: Wed, 6 Aug 2025 03:57:45 +0700 Subject: [PATCH] fix: inventory --- package-lock.json | 13 + package.json | 1 + .../apps/ecommerce/orders/list/page.tsx | 5 +- .../apps/ecommerce/products/list/page.tsx | 3 - .../(private)/apps/stock/adjustment/page.tsx | 30 ++ .../(private)/apps/stock/list/page.tsx | 30 ++ .../dialogs/confirm-delete/index.tsx | 38 ++ .../layout/vertical/VerticalMenu.tsx | 4 + src/data/dictionaries/ar.json | 2 + src/data/dictionaries/en.json | 2 + src/data/dictionaries/fr.json | 2 + src/services/mutations/categories.ts | 60 +++ src/services/mutations/inventories.ts | 60 +++ src/services/mutations/products.ts | 39 +- src/services/queries/categories.ts | 2 - src/services/queries/inventories.ts | 39 ++ src/services/queries/orders.ts | 39 ++ src/services/queries/outlets.ts | 39 ++ src/services/queries/products.ts | 11 + src/types/services/category.ts | 7 + src/types/services/inventory.ts | 30 ++ src/types/services/order.ts | 45 ++ src/types/services/outlet.ts | 21 + src/types/services/paymentMethod.ts | 19 + .../ecommerce/orders/list/OrderListTable.tsx | 390 +++++++++-------- .../apps/ecommerce/orders/list/index.tsx | 5 +- .../products/add/ProductAddHeader.tsx | 37 +- .../products/add/ProductInformation.tsx | 24 +- .../products/category/AddCategoryDrawer.tsx | 182 +++----- .../products/category/EditCategoryDrawer.tsx | 137 ++++++ .../category/ProductCategoryTable.tsx | 50 ++- .../products/list/ProductListTable.tsx | 31 +- .../adjustment/AdjustmentStockDrawer.tsx | 203 +++++++++ .../apps/stock/adjustment/StockListTable.tsx | 392 ++++++++++++++++++ src/views/apps/stock/list/AddStockDrawer.tsx | 191 +++++++++ src/views/apps/stock/list/StockListTable.tsx | 392 ++++++++++++++++++ src/views/apps/stock/list/TableFilters.tsx | 107 +++++ 37 files changed, 2342 insertions(+), 340 deletions(-) create mode 100644 src/app/[lang]/(dashboard)/(private)/apps/stock/adjustment/page.tsx create mode 100644 src/app/[lang]/(dashboard)/(private)/apps/stock/list/page.tsx create mode 100644 src/components/dialogs/confirm-delete/index.tsx create mode 100644 src/services/mutations/categories.ts create mode 100644 src/services/mutations/inventories.ts create mode 100644 src/services/queries/inventories.ts create mode 100644 src/services/queries/orders.ts create mode 100644 src/services/queries/outlets.ts create mode 100644 src/types/services/inventory.ts create mode 100644 src/types/services/order.ts create mode 100644 src/types/services/outlet.ts create mode 100644 src/types/services/paymentMethod.ts create mode 100644 src/views/apps/ecommerce/products/category/EditCategoryDrawer.tsx create mode 100644 src/views/apps/stock/adjustment/AdjustmentStockDrawer.tsx create mode 100644 src/views/apps/stock/adjustment/StockListTable.tsx create mode 100644 src/views/apps/stock/list/AddStockDrawer.tsx create mode 100644 src/views/apps/stock/list/StockListTable.tsx create mode 100644 src/views/apps/stock/list/TableFilters.tsx diff --git a/package-lock.json b/package-lock.json index 4234895..c18ef60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "react-toastify": "10.0.6", "react-use": "17.6.0", "recharts": "2.15.0", + "use-debounce": "^10.0.5", "valibot": "0.42.1" }, "devDependencies": { @@ -12579,6 +12580,18 @@ } } }, + "node_modules/use-debounce": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.5.tgz", + "integrity": "sha512-Q76E3lnIV+4YT9AHcrHEHYmAd9LKwUAbPXDm7FlqVGDHiSOhX3RDjT8dm0AxbJup6WgOb1YEcKyCr11kBJR5KQ==", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", diff --git a/package.json b/package.json index 314b9c6..abbf895 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "react-toastify": "10.0.6", "react-use": "17.6.0", "recharts": "2.15.0", + "use-debounce": "^10.0.5", "valibot": "0.42.1" }, "devDependencies": { diff --git a/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/list/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/list/page.tsx index 0d4ec9c..68ac6d3 100644 --- a/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/list/page.tsx +++ b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/list/page.tsx @@ -2,7 +2,6 @@ import OrderList from '@views/apps/ecommerce/orders/list' // Data Imports -import { getEcommerceData } from '@/app/server/actions' /** * ! If you need data using an API call, uncomment the below API code, update the `process.env.API_URL` variable in the @@ -23,10 +22,8 @@ import { getEcommerceData } from '@/app/server/actions' } */ const OrdersListPage = async () => { - // Vars - const data = await getEcommerceData() - return + return } export default OrdersListPage diff --git a/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/products/list/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/products/list/page.tsx index decf05d..988f940 100644 --- a/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/products/list/page.tsx +++ b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/products/list/page.tsx @@ -6,7 +6,6 @@ import ProductCard from '@views/apps/ecommerce/products/list/ProductCard' import ProductListTable from '@views/apps/ecommerce/products/list/ProductListTable' // Data Imports -import { getEcommerceData } from '@/app/server/actions' /** * ! If you need data using an API call, uncomment the below API code, update the `process.env.API_URL` variable in the @@ -27,8 +26,6 @@ import { getEcommerceData } from '@/app/server/actions' } */ const eCommerceProductsList = async () => { - // Vars - const data = await getEcommerceData() return ( diff --git a/src/app/[lang]/(dashboard)/(private)/apps/stock/adjustment/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/stock/adjustment/page.tsx new file mode 100644 index 0000000..e318966 --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/stock/adjustment/page.tsx @@ -0,0 +1,30 @@ +// MUI Imports + +// Component Imports +import StockListTable from '../../../../../../../views/apps/stock/adjustment/StockListTable' + +// Data Imports + +/** + * ! 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 StockAdjustment = async () => { + return +} + +export default StockAdjustment diff --git a/src/app/[lang]/(dashboard)/(private)/apps/stock/list/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/stock/list/page.tsx new file mode 100644 index 0000000..d5b379b --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/stock/list/page.tsx @@ -0,0 +1,30 @@ +// MUI Imports + +// Component Imports +import StockListTable from '../../../../../../../views/apps/stock/list/StockListTable' + +// Data Imports + +/** + * ! 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 StockList = async () => { + return +} + +export default StockList diff --git a/src/components/dialogs/confirm-delete/index.tsx b/src/components/dialogs/confirm-delete/index.tsx new file mode 100644 index 0000000..7879202 --- /dev/null +++ b/src/components/dialogs/confirm-delete/index.tsx @@ -0,0 +1,38 @@ +import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material' + +interface ConfirmDeleteDialogProps { + open: boolean + onClose: () => void + onConfirm: () => void + isLoading?: boolean + title?: string + message?: string +} + +const ConfirmDeleteDialog = ({ + open, + onClose, + onConfirm, + isLoading, + title = 'Delete Product', + message = 'Are you sure you want to delete this product? This action cannot be undone.' +}: ConfirmDeleteDialogProps) => { + return ( + + {title} + + {message} + + + + + + + ) +} + +export default ConfirmDeleteDialog diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx index 4e290f0..34e8b07 100644 --- a/src/components/layout/vertical/VerticalMenu.tsx +++ b/src/components/layout/vertical/VerticalMenu.tsx @@ -145,6 +145,10 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => { {dictionary['navigation'].referrals} {dictionary['navigation'].settings} + }> + {dictionary['navigation'].list} + {dictionary['navigation'].addjustment} + }> {dictionary['navigation'].dashboard} {dictionary['navigation'].myCourses} diff --git a/src/data/dictionaries/ar.json b/src/data/dictionaries/ar.json index 6b18001..556938f 100644 --- a/src/data/dictionaries/ar.json +++ b/src/data/dictionaries/ar.json @@ -4,6 +4,7 @@ "crm": "إدارة علاقات العملاء", "analytics": "تحليلات", "eCommerce": "التجارة الإلكترونية", + "stock": "المخزون", "academy": "أكاديمية", "logistics": "اللوجستية", "frontPages": "الصفحات الأولى", @@ -18,6 +19,7 @@ "products": "منتجات", "list": "قائمة", "add": "يضيف", + "addjustment": "تعديل", "category": "فئة", "orders": "أوامر", "details": "تفاصيل", diff --git a/src/data/dictionaries/en.json b/src/data/dictionaries/en.json index d32e680..f11d7ea 100644 --- a/src/data/dictionaries/en.json +++ b/src/data/dictionaries/en.json @@ -4,6 +4,7 @@ "crm": "CRM", "analytics": "Analytics", "eCommerce": "eCommerce", + "stock": "Stock", "academy": "Academy", "logistics": "Logistics", "frontPages": "Front Pages", @@ -18,6 +19,7 @@ "products": "Products", "list": "List", "add": "Add", + "addjustment": "Addjustment", "category": "Category", "orders": "Orders", "details": "Details", diff --git a/src/data/dictionaries/fr.json b/src/data/dictionaries/fr.json index 17bef06..5e6a580 100644 --- a/src/data/dictionaries/fr.json +++ b/src/data/dictionaries/fr.json @@ -4,6 +4,7 @@ "crm": "GRC", "analytics": "Analytique", "eCommerce": "commerce électronique", + "stock": "Stock", "academy": "Académie", "logistics": "Logistique", "frontPages": "Premières pages", @@ -18,6 +19,7 @@ "products": "Produits", "list": "Liste", "add": "Ajouter", + "addjustment": "Ajustement", "category": "Catégorie", "orders": "Ordres", "details": "Détails", diff --git a/src/services/mutations/categories.ts b/src/services/mutations/categories.ts new file mode 100644 index 0000000..238e613 --- /dev/null +++ b/src/services/mutations/categories.ts @@ -0,0 +1,60 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { api } from '../api' +import { toast } from 'react-toastify' +import { CategoryRequest } from '../../types/services/category' + +export const useCategoriesMutation = { + createCategory: () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (newCategory: CategoryRequest) => { + const response = await api.post('/categories', newCategory) + return response.data + }, + onSuccess: () => { + toast.success('Category created successfully!') + queryClient.invalidateQueries({ queryKey: ['categories'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') + } + }) + }, + + updateCategory: () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ id, payload }: { id: string; payload: CategoryRequest }) => { + const response = await api.put(`/categories/${id}`, payload) + return response.data + }, + onSuccess: () => { + toast.success('Category updated successfully!') + queryClient.invalidateQueries({ queryKey: ['categories'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed') + } + }) + }, + + deleteCategory: () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (id: string) => { + const response = await api.delete(`/categories/${id}`) + return response.data + }, + onSuccess: () => { + toast.success('Category deleted successfully!') + queryClient.invalidateQueries({ queryKey: ['categories'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed') + } + }) + } +} diff --git a/src/services/mutations/inventories.ts b/src/services/mutations/inventories.ts new file mode 100644 index 0000000..45a49cb --- /dev/null +++ b/src/services/mutations/inventories.ts @@ -0,0 +1,60 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { api } from '../api' +import { toast } from 'react-toastify' +import { InventoryAdjustRequest, InventoryRequest } from '../../types/services/inventory' + +export const useInventoriesMutation = { + createInventory: () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (newInventory: InventoryRequest) => { + const response = await api.post('/inventory', newInventory) + return response.data + }, + onSuccess: () => { + toast.success('Inventory created successfully!') + queryClient.invalidateQueries({ queryKey: ['inventories'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') + } + }) + }, + + adjustInventory: () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (newInventory: InventoryAdjustRequest) => { + const response = await api.post('/inventory/adjust', newInventory) + return response.data + }, + onSuccess: () => { + toast.success('Inventory adjusted successfully!') + queryClient.invalidateQueries({ queryKey: ['inventories'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') + } + }) + }, + + deleteInventory: () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (id: string) => { + const response = await api.delete(`/inventory/${id}`) + return response.data + }, + onSuccess: () => { + toast.success('Inventory deleted successfully!') + queryClient.invalidateQueries({ queryKey: ['inventories'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed') + } + }) + } +} diff --git a/src/services/mutations/products.ts b/src/services/mutations/products.ts index a28d464..16aaef3 100644 --- a/src/services/mutations/products.ts +++ b/src/services/mutations/products.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query' +import { useMutation, useQueryClient } from '@tanstack/react-query' import { api } from '../api' import { toast } from 'react-toastify' import { ProductRequest } from '../../types/services/product' @@ -10,11 +10,44 @@ export const useProductsMutation = { const response = await api.post('/products', newProduct) return response.data }, - onSuccess: data => { + onSuccess: () => { toast.success('Product created successfully!') }, onError: (error: any) => { - toast.error(error.response.data.errors[0].cause) + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') + } + }) + }, + + updateProduct: () => { + return useMutation({ + mutationFn: async ({ id, payload }: { id: string; payload: ProductRequest }) => { + const response = await api.put(`/products/${id}`, payload) + return response.data + }, + onSuccess: () => { + toast.success('Product updated successfully!') + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed') + } + }) + }, + + deleteProduct: () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (id: string) => { + const response = await api.delete(`/products/${id}`) + return response.data + }, + onSuccess: () => { + toast.success('Product deleted successfully!') + queryClient.invalidateQueries({ queryKey: ['products'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed') } }) } diff --git a/src/services/queries/categories.ts b/src/services/queries/categories.ts index 645ac46..f777b77 100644 --- a/src/services/queries/categories.ts +++ b/src/services/queries/categories.ts @@ -34,8 +34,6 @@ export const useCategoriesQuery = { const res = await api.get(`/categories?${queryParams.toString()}`) return res.data.data }, - // Cache for 5 minutes - staleTime: 5 * 60 * 1000 }) } } diff --git a/src/services/queries/inventories.ts b/src/services/queries/inventories.ts new file mode 100644 index 0000000..7498be7 --- /dev/null +++ b/src/services/queries/inventories.ts @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query" +import { Inventories } from "../../types/services/inventory" +import { api } from "../api" + +interface InventoriesQueryParams { + page?: number + limit?: number + search?: string +} + +export const useInventoriesQuery = { + getInventories: (params: InventoriesQueryParams = {}) => { + const { page = 1, limit = 10, search = '', ...filters } = params + + return useQuery({ + queryKey: ['inventories', { 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) + } + + // Add other filters + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/inventory?${queryParams.toString()}`) + return res.data.data + }, + }) + } +} diff --git a/src/services/queries/orders.ts b/src/services/queries/orders.ts new file mode 100644 index 0000000..c88a85b --- /dev/null +++ b/src/services/queries/orders.ts @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query" +import { Orders } from "../../types/services/order" +import { api } from "../api" + +interface OrdersQueryParams { + page?: number + limit?: number + search?: string +} + +export const useOrdersQuery = { + getOrders: (params: OrdersQueryParams = {}) => { + const { page = 1, limit = 10, search = '', ...filters } = params + + return useQuery({ + queryKey: ['orders', { 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) + } + + // Add other filters + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/orders?${queryParams.toString()}`) + return res.data.data + }, + }) + } +} diff --git a/src/services/queries/outlets.ts b/src/services/queries/outlets.ts new file mode 100644 index 0000000..5d824cd --- /dev/null +++ b/src/services/queries/outlets.ts @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query" +import { Outlets } from "../../types/services/outlet" +import { api } from "../api" + +interface OutletsQueryParams { + page?: number + limit?: number + search?: string +} + +export const useOutletsQuery = { + getOutlets: (params: OutletsQueryParams = {}) => { + const { page = 1, limit = 10, search = '', ...filters } = params + + return useQuery({ + queryKey: ['outlets', { 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) + } + + // Add other filters + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/outlets/list?${queryParams.toString()}`) + return res.data.data + } + }) + } +} diff --git a/src/services/queries/products.ts b/src/services/queries/products.ts index f32421d..b61a2f2 100644 --- a/src/services/queries/products.ts +++ b/src/services/queries/products.ts @@ -37,6 +37,17 @@ export const useProductsQuery = { const res = await api.get(`/products?${queryParams.toString()}`) return res.data.data }, + }) + }, + + getProductById: (id: string) => { + return useQuery({ + queryKey: ['product', id], + queryFn: async ({ queryKey: [, id] }) => { + const res = await api.get(`/products/${id}`) + return res.data.data + }, + // Cache for 5 minutes staleTime: 5 * 60 * 1000 }) diff --git a/src/types/services/category.ts b/src/types/services/category.ts index 5978a41..d42b222 100644 --- a/src/types/services/category.ts +++ b/src/types/services/category.ts @@ -16,3 +16,10 @@ export interface Categories { limit: number; total_pages: number; } + + +export interface CategoryRequest { + name: string; + description: string | null; + business_type: string; +} diff --git a/src/types/services/inventory.ts b/src/types/services/inventory.ts new file mode 100644 index 0000000..4f5f0d8 --- /dev/null +++ b/src/types/services/inventory.ts @@ -0,0 +1,30 @@ +export interface Inventories { + inventory: Inventory[] + total_count: number + page: number + limit: number + total_pages: number +} + +export interface Inventory { + id: string + outlet_id: string + product_id: string + quantity: number + reorder_level: number + is_low_stock: boolean + updated_at: string // ISO 8601 timestamp +} + +export interface InventoryRequest { + product_id: string + outlet_id: string + quantity: number +} + +export interface InventoryAdjustRequest { + product_id: string + outlet_id: string + delta: number + reason: string +} diff --git a/src/types/services/order.ts b/src/types/services/order.ts new file mode 100644 index 0000000..32d2755 --- /dev/null +++ b/src/types/services/order.ts @@ -0,0 +1,45 @@ +export interface Orders { + orders: Order[] + total_count: number + page: number + limit: number + total_pages: number +} + +export interface Order { + id: string + order_number: string + outlet_id: string + user_id: string + table_number: string + order_type: 'dineIn' | 'takeAway' | 'delivery' + status: 'pending' | 'inProgress' | 'completed' | 'cancelled' + subtotal: number + tax_amount: number + discount_amount: number + total_amount: number + notes: string | null + metadata: { + customer_name: string + } + created_at: string + updated_at: string + order_items: OrderItem[] +} + +export interface OrderItem { + id: string + order_id: string + product_id: string + product_name: string + product_variant_id: string | null + product_variant_name?: string + quantity: number + unit_price: number + total_price: number + modifiers: any[] + notes: string + status: 'pending' | 'completed' | 'cancelled' + created_at: string + updated_at: string +} diff --git a/src/types/services/outlet.ts b/src/types/services/outlet.ts new file mode 100644 index 0000000..4b66601 --- /dev/null +++ b/src/types/services/outlet.ts @@ -0,0 +1,21 @@ +export interface Outlet { + id: string; + organization_id: string; + name: string; + address: string; + phone_number: string | null; + business_type: 'restaurant' | 'retail' | string; // sesuaikan jika ada enum yang lebih pasti + currency: string; + tax_rate: number; + is_active: boolean; + created_at: string; + updated_at: string; +} + +export interface Outlets { + outlets: Outlet[]; + total_count: number; + page: number; + limit: number; + total_pages: number; +} diff --git a/src/types/services/paymentMethod.ts b/src/types/services/paymentMethod.ts new file mode 100644 index 0000000..6cdf6dd --- /dev/null +++ b/src/types/services/paymentMethod.ts @@ -0,0 +1,19 @@ +export interface PaymentMethods { + payment_methods: PaymentMethod[]; + total_count: number; + page: number; + limit: number; + total_pages: number; +} + +export interface PaymentMethod { + id: string; + organization_id: string; + name: string; + type: PaymentMethodType; + is_active: boolean; + created_at: string; // ISO 8601 timestamp + updated_at: string; // ISO 8601 timestamp +} + +export type PaymentMethodType = "cash" | "card" | "edc" | "delivery" | string; diff --git a/src/views/apps/ecommerce/orders/list/OrderListTable.tsx b/src/views/apps/ecommerce/orders/list/OrderListTable.tsx index 2acc548..ec3aa03 100644 --- a/src/views/apps/ecommerce/orders/list/OrderListTable.tsx +++ b/src/views/apps/ecommerce/orders/list/OrderListTable.tsx @@ -1,49 +1,38 @@ // React Imports -import { useState, useEffect, useMemo } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' // Next Imports import Link from 'next/link' import { useParams } from 'next/navigation' // MUI Imports +import Button from '@mui/material/Button' import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' -import Button from '@mui/material/Button' -import Typography from '@mui/material/Typography' import Checkbox from '@mui/material/Checkbox' import Chip from '@mui/material/Chip' +import MenuItem from '@mui/material/MenuItem' import TablePagination from '@mui/material/TablePagination' import type { TextFieldProps } from '@mui/material/TextField' -import MenuItem from '@mui/material/MenuItem' +import Typography from '@mui/material/Typography' // Third-party Imports -import classnames from 'classnames' -import { rankItem } from '@tanstack/match-sorter-utils' -import { - createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, - getFilteredRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFacetedMinMaxValues, - getPaginationRowModel, - getSortedRowModel -} from '@tanstack/react-table' -import type { ColumnDef, FilterFn } from '@tanstack/react-table' 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 -import type { ThemeColor } from '@core/types' import type { OrderType } from '@/types/apps/ecommerceTypes' import type { Locale } from '@configs/i18n' +import type { ThemeColor } from '@core/types' // Component Imports -import CustomAvatar from '@core/components/mui/Avatar' -import OptionMenu from '@core/components/option-menu' -import CustomTextField from '@core/components/mui/TextField' import TablePaginationComponent from '@components/TablePaginationComponent' +import CustomAvatar from '@core/components/mui/Avatar' +import CustomTextField from '@core/components/mui/TextField' +import OptionMenu from '@core/components/option-menu' // Util Imports import { getInitials } from '@/utils/getInitials' @@ -51,6 +40,10 @@ import { getLocalizedUrl } from '@/utils/i18n' // Style Imports import tableStyles from '@core/styles/table.module.css' +import { useOrdersQuery } from '../../../../../services/queries/orders' +import { Order } from '../../../../../types/services/order' +import { Box, CircularProgress } from '@mui/material' +import Loading from '../../../../../components/layout/shared/Loading' declare module '@tanstack/table-core' { interface FilterFns { @@ -85,7 +78,7 @@ export const statusChipColor: { [key: string]: StatusChipColorType } = { Dispatched: { color: 'warning' } } -type ECommerceOrderTypeWithAction = OrderType & { +type ECommerceOrderTypeWithAction = Order & { action?: string } @@ -134,15 +127,34 @@ const DebouncedInput = ({ // Column Definitions const columnHelper = createColumnHelper() -const OrderListTable = ({ orderData }: { orderData?: OrderType[] }) => { +const OrderListTable = () => { // States const [rowSelection, setRowSelection] = useState({}) - const [data, setData] = useState(...[orderData]) - const [globalFilter, setGlobalFilter] = useState('') + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + + const { data, isLoading, error, isFetching } = useOrdersQuery.getOrders({ + page: currentPage, + limit: pageSize + }) // Hooks const { lang: locale } = useParams() + const orders = data?.orders ?? [] + 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(0) // Reset to first page + }, []) + // Vars const paypal = '/images/apps/ecommerce/paypal.png' const mastercard = '/images/apps/ecommerce/mastercard.png' @@ -171,84 +183,95 @@ const OrderListTable = ({ orderData }: { orderData?: OrderType[] }) => { /> ) }, - columnHelper.accessor('order', { - header: 'Order', + columnHelper.accessor('order_number', { + header: 'Order Number', cell: ({ row }) => ( {`#${row.original.order}`} + >{`#${row.original.order_number}`} ) }), - columnHelper.accessor('date', { - header: 'Date', - cell: ({ row }) => ( - {`${new Date(row.original.date).toDateString()}, ${row.original.time}`} - ) + columnHelper.accessor('table_number', { + header: 'Table', + cell: ({ row }) => {row.original.table_number} }), - columnHelper.accessor('customer', { - header: 'Customers', - cell: ({ row }) => ( -
- {getAvatar({ avatar: row.original.avatar, customer: row.original.customer })} -
- - {row.original.customer} - - {row.original.email} -
-
- ) - }), - columnHelper.accessor('payment', { - header: 'Payment', - cell: ({ row }) => ( -
- - - {paymentStatus[row.original.payment].text} - -
- ) + columnHelper.accessor('order_type', { + header: 'Order Type', + cell: ({ row }) => {row.original.order_type} }), + // columnHelper.accessor('order_type', { + // header: 'Customers', + // cell: ({ row }) => ( + //
+ // {getAvatar({ avatar: row.original.avatar, customer: row.original.customer })} + //
+ // + // {row.original.customer} + // + // {row.original.email} + //
+ //
+ // ) + // }), + // columnHelper.accessor('payment', { + // header: 'Payment', + // cell: ({ row }) => ( + //
+ // + // + // {paymentStatus[row.original.payment].text} + // + //
+ // ) + // }), columnHelper.accessor('status', { header: 'Status', - cell: ({ row }) => ( - - ) + cell: ({ row }) => }), - columnHelper.accessor('method', { - header: 'Method', - cell: ({ row }) => ( -
-
- -
- - {`...${row.original.method === 'mastercard' ? row.original.methodNumber : '@gmail.com'}`} - -
- ) + columnHelper.accessor('subtotal', { + header: 'SubTotal', + cell: ({ row }) => {row.original.subtotal} }), + columnHelper.accessor('total_amount', { + header: 'Total', + cell: ({ row }) => {row.original.total_amount} + }), + columnHelper.accessor('tax_amount', { + header: 'Tax', + cell: ({ row }) => {row.original.tax_amount} + }), + columnHelper.accessor('discount_amount', { + header: 'Discount', + cell: ({ row }) => {row.original.discount_amount} + }), + // columnHelper.accessor('method', { + // header: 'Method', + // cell: ({ row }) => ( + //
+ //
+ // + //
+ // + // {`...${row.original.method === 'mastercard' ? row.original.methodNumber : '@gmail.com'}`} + // + //
+ // ) + // }), columnHelper.accessor('action', { header: 'Action', cell: ({ row }) => ( @@ -260,14 +283,17 @@ const OrderListTable = ({ orderData }: { orderData?: OrderType[] }) => { { text: 'View', icon: 'tabler-eye', - href: getLocalizedUrl(`/apps/ecommerce/orders/details/${row.original.order}`, locale as Locale), + href: getLocalizedUrl( + `/apps/ecommerce/orders/details/${row.original.order_number}`, + locale as Locale + ), linkProps: { className: 'flex items-center gap-2 is-full plb-2 pli-4' } }, { text: 'Delete', icon: 'tabler-trash', menuItemProps: { - onClick: () => setData(data?.filter(order => order.id !== row.original.id)), + onClick: () => {}, className: 'flex items-center' } } @@ -283,32 +309,24 @@ const OrderListTable = ({ orderData }: { orderData?: OrderType[] }) => { ) const table = useReactTable({ - data: data as OrderType[], + data: orders as Order[], columns, filterFns: { fuzzy: fuzzyFilter }, state: { rowSelection, - globalFilter - }, - initialState: { pagination: { - pageSize: 10 + pageIndex: currentPage, // <= penting! + pageSize } }, enableRowSelection: true, //enable row selection for all rows - // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row - globalFilterFn: fuzzyFilter, onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), - onGlobalFilterChange: setGlobalFilter, - getFilteredRowModel: getFilteredRowModel(), - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - getFacetedMinMaxValues: getFacetedMinMaxValues() + // Disable client-side pagination since we're handling it server-side + manualPagination: true, + pageCount: Math.ceil(totalCount / pageSize) }) const getAvatar = (params: Pick) => { @@ -329,8 +347,8 @@ const OrderListTable = ({ orderData }: { orderData?: OrderType[] }) => { setGlobalFilter(String(value))} + value={''} + onChange={value => console.log('click')} placeholder='Search Order' className='sm:is-auto' /> @@ -357,68 +375,98 @@ const OrderListTable = ({ orderData }: { orderData?: OrderType[] }) => {
- - - {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())}
+ {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={table.getFilteredRowModel().rows.length} - rowsPerPage={table.getState().pagination.pageSize} - page={table.getState().pagination.pageIndex} - onPageChange={(_, page) => { - table.setPageIndex(page) - }} + component={() => ( + + )} + count={totalCount} + rowsPerPage={pageSize} + page={currentPage} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + disabled={isLoading} />
) diff --git a/src/views/apps/ecommerce/orders/list/index.tsx b/src/views/apps/ecommerce/orders/list/index.tsx index af7c500..da49cea 100644 --- a/src/views/apps/ecommerce/orders/list/index.tsx +++ b/src/views/apps/ecommerce/orders/list/index.tsx @@ -4,20 +4,19 @@ import Grid from '@mui/material/Grid2' // Type Imports -import type { OrderType } from '@/types/apps/ecommerceTypes' // Component Imports import OrderCard from './OrderCard' import OrderListTable from './OrderListTable' -const OrderList = ({ orderData }: { orderData?: OrderType[] }) => { +const OrderList = () => { return ( - + ) diff --git a/src/views/apps/ecommerce/products/add/ProductAddHeader.tsx b/src/views/apps/ecommerce/products/add/ProductAddHeader.tsx index eac9907..e6c5b77 100644 --- a/src/views/apps/ecommerce/products/add/ProductAddHeader.tsx +++ b/src/views/apps/ecommerce/products/add/ProductAddHeader.tsx @@ -8,28 +8,46 @@ import { useDispatch, useSelector } from 'react-redux' import { RootState } from '../../../../../redux-store' import { CircularProgress } from '@mui/material' import { resetProduct } from '../../../../../redux-store/slices/product' +import { useParams } from 'next/navigation' const ProductAddHeader = () => { const dispatch = useDispatch() - const { mutate, isPending } = useProductsMutation.createProduct() + const params = useParams() + + const { mutate: createProduct, isPending: isCreating } = useProductsMutation.createProduct() + const { mutate: updateProduct, isPending: isUpdating } = useProductsMutation.updateProduct() + const { productRequest } = useSelector((state: RootState) => state.productReducer) + const isEdit = !!params?.id + const handleSubmit = () => { const { cost, price, ...rest } = productRequest const newProductRequest = { ...rest, cost: Number(cost), price: Number(price) } - mutate(newProductRequest, { - onSuccess: () => { - dispatch(resetProduct()) - } - }) + if (isEdit) { + updateProduct( + { id: params?.id as string, payload: newProductRequest }, + { + onSuccess: () => { + dispatch(resetProduct()) + } + } + ) + } else { + createProduct(newProductRequest, { + onSuccess: () => { + dispatch(resetProduct()) + } + }) + } } return (
- Add a new product + {isEdit ? 'Edit Product' : 'Add a new product'} Orders placed across your store
@@ -38,8 +56,9 @@ const ProductAddHeader = () => { Discard -
diff --git a/src/views/apps/ecommerce/products/add/ProductInformation.tsx b/src/views/apps/ecommerce/products/add/ProductInformation.tsx index 6b04d7e..4e4495f 100644 --- a/src/views/apps/ecommerce/products/add/ProductInformation.tsx +++ b/src/views/apps/ecommerce/products/add/ProductInformation.tsx @@ -26,8 +26,11 @@ import '@/libs/styles/tiptapEditor.css' import { useDispatch, useSelector } from 'react-redux' import { RootState } from '../../../../../redux-store' -import { setProductField } from '@/redux-store/slices/product' +import { setProduct, setProductField } from '@/redux-store/slices/product' import { useEffect } from 'react' +import { useParams } from 'next/navigation' +import { useProductsQuery } from '../../../../../services/queries/products' +import Loading from '../../../../../components/layout/shared/Loading' const EditorToolbar = ({ editor }: { editor: Editor | null }) => { if (!editor) { @@ -120,8 +123,19 @@ const EditorToolbar = ({ editor }: { editor: Editor | null }) => { const ProductInformation = () => { const dispatch = useDispatch() + const params = useParams() + + const { data: product, isLoading, error } = useProductsQuery.getProductById(params?.id as string) const { name, sku, barcode, description } = useSelector((state: RootState) => state.productReducer.productRequest) + const isEdit = !!params?.id + + useEffect(() => { + if (product) { + dispatch(setProduct(product)) + } + }, [product, dispatch]) + const handleInputChange = (field: any, value: any) => { dispatch(setProductField({ field, value })) } @@ -160,6 +174,8 @@ const ProductInformation = () => { } }, [editor]) + if (isLoading) return + return ( @@ -170,7 +186,7 @@ const ProductInformation = () => { fullWidth label='Product Name' placeholder='iPhone 14' - value={name} + value={name || ''} onChange={e => handleInputChange('name', e.target.value)} />
@@ -179,7 +195,7 @@ const ProductInformation = () => { fullWidth label='SKU' placeholder='FXSK123U' - value={sku} + value={sku || ''} onChange={e => handleInputChange('sku', e.target.value)} /> @@ -188,7 +204,7 @@ const ProductInformation = () => { fullWidth label='Barcode' placeholder='0123-4567' - value={barcode} + value={barcode || ''} onChange={e => handleInputChange('barcode', e.target.value)} /> diff --git a/src/views/apps/ecommerce/products/category/AddCategoryDrawer.tsx b/src/views/apps/ecommerce/products/category/AddCategoryDrawer.tsx index 8d0cc6f..69a7824 100644 --- a/src/views/apps/ecommerce/products/category/AddCategoryDrawer.tsx +++ b/src/views/apps/ecommerce/products/category/AddCategoryDrawer.tsx @@ -1,95 +1,67 @@ // React Imports -import { useState, useRef } from 'react' -import type { ChangeEvent } from 'react' +import { 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 MenuItem from '@mui/material/MenuItem' import Typography from '@mui/material/Typography' -import Divider from '@mui/material/Divider' -import InputAdornment from '@mui/material/InputAdornment' // Third-party Imports -import { useForm, Controller } from 'react-hook-form' // Type Imports -import type { categoryType } from './ProductCategoryTable' // Components Imports import CustomTextField from '@core/components/mui/TextField' +import { useCategoriesMutation } from '../../../../../services/mutations/categories' +import { CategoryRequest } from '../../../../../types/services/category' type Props = { open: boolean handleClose: () => void - categoryData: categoryType[] - setData: (data: categoryType[]) => void -} - -type FormValues = { - title: string - description: string } const AddCategoryDrawer = (props: Props) => { // Props - const { open, handleClose, categoryData, setData } = props + const { open, handleClose } = props + + const { mutate: createCategory, isPending: isCreating } = useCategoriesMutation.createCategory() // States - const [fileName, setFileName] = useState('') - const [category, setCategory] = useState('') - const [comment, setComment] = useState('') - const [status, setStatus] = useState('') - - // Refs - const fileInputRef = useRef(null) - - // Hooks - const { - control, - reset: resetForm, - handleSubmit, - formState: { errors } - } = useForm({ - defaultValues: { - title: '', - description: '' - } + const [formData, setFormData] = useState({ + name: '', + description: '', + business_type: '' }) // Handle Form Submit - const handleFormSubmit = (data: FormValues) => { - const newData = { - id: categoryData.length + 1, - categoryTitle: data.title, - description: data.description, - totalProduct: Math.floor(Math.random() * 9000) + 1000, - totalEarning: Math.floor(Math.random() * 90000) + 10000, - image: `/images/apps/ecommerce/product-${Math.floor(Math.random() * 20) + 1}.png` - } + const handleFormSubmit = (e: any) => { + e.preventDefault() - setData([...categoryData, newData]) - handleReset() + createCategory(formData, { + onSuccess: () => { + handleReset() + } + }) + } + + const handleInputChange = (e: any) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }) } // Handle Form Reset const handleReset = () => { handleClose() - resetForm({ title: '', description: '' }) - setFileName('') - setCategory('') - setComment('') - setStatus('') - } - - // Handle File Upload - const handleFileUpload = (event: ChangeEvent) => { - const { files } = event.target - - if (files && files.length !== 0) { - setFileName(files[0].name) - } + setFormData({ + name: '', + description: '', + business_type: '' + }) } return ( @@ -109,95 +81,37 @@ const AddCategoryDrawer = (props: Props) => {
-
handleFormSubmit(data))} className='flex flex-col gap-5'> - ( - - )} + + - ( - - )} - /> -
- - setFileName('')}> - - - - ) : null - } - }} - /> - -
setCategory(e.target.value)} + label='Business Type' + value={formData.business_type} + onChange={e => setFormData({ ...formData, business_type: e.target.value })} > - HouseHold - Management - Electronics - Office - Accessories + Restaurant setComment(e.target.value)} + label='Description' + value={formData.description} + name='description' + onChange={handleInputChange} multiline rows={4} placeholder='Write a Comment...' /> - setStatus(e.target.value)} - > - Published - Inactive - Scheduled -
- + +
+ +
+ + ) +} + +export default EditCategoryDrawer diff --git a/src/views/apps/ecommerce/products/category/ProductCategoryTable.tsx b/src/views/apps/ecommerce/products/category/ProductCategoryTable.tsx index baa9f6e..8464883 100644 --- a/src/views/apps/ecommerce/products/category/ProductCategoryTable.tsx +++ b/src/views/apps/ecommerce/products/category/ProductCategoryTable.tsx @@ -43,6 +43,9 @@ import { useCategoriesQuery } from '../../../../../services/queries/categories' import { Category } from '../../../../../types/services/category' import { Box, CircularProgress } from '@mui/material' import Loading from '../../../../../components/layout/shared/Loading' +import { useCategoriesMutation } from '../../../../../services/mutations/categories' +import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' +import EditCategoryDrawer from './EditCategoryDrawer' declare module '@tanstack/table-core' { interface FilterFns { @@ -105,9 +108,13 @@ const columnHelper = createColumnHelper() const ProductCategoryTable = () => { // States const [addCategoryOpen, setAddCategoryOpen] = useState(false) + const [editCategoryOpen, setEditCategoryOpen] = useState(false) const [rowSelection, setRowSelection] = useState({}) const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(10) + const [categoryId, setCategoryId] = useState('') + const [openConfirm, setOpenConfirm] = useState(false) + const [currentCategory, setCurrentCategory] = useState() // Fetch products with pagination and search const { data, isLoading, error, isFetching } = useCategoriesQuery.getCategories({ @@ -115,6 +122,8 @@ const ProductCategoryTable = () => { limit: pageSize }) + const { mutate: deleteCategory, isPending: isDeleting } = useCategoriesMutation.deleteCategory() + const categories = data?.categories ?? [] const totalCount = data?.total_count ?? 0 @@ -129,6 +138,12 @@ const ProductCategoryTable = () => { setCurrentPage(0) // Reset to first page }, []) + const handleDelete = () => { + deleteCategory(categoryId, { + onSuccess: () => setOpenConfirm(false) + }) + } + const columns = useMemo[]>( () => [ { @@ -162,7 +177,6 @@ const ProductCategoryTable = () => { {row.original.name} - {row.original.description} ) @@ -175,11 +189,18 @@ const ProductCategoryTable = () => { header: 'Business Type', cell: ({ row }) => {row.original.business_type} }), + columnHelper.accessor('created_at', { + header: 'Created At', + cell: ({ row }) => {row.original.created_at} + }), columnHelper.accessor('actions', { header: 'Actions', cell: ({ row }) => (
- + { + setCurrentCategory(row.original) + setEditCategoryOpen(!editCategoryOpen) + }}> { { text: 'Delete', icon: 'tabler-trash', - menuItemProps: { onClick: () => console.log('click') } + menuItemProps: { + onClick: () => { + setCategoryId(row.original.id) + setOpenConfirm(true) + } + } }, { text: 'Duplicate', icon: 'tabler-copy' } ]} @@ -350,12 +376,26 @@ const ProductCategoryTable = () => { disabled={isLoading} /> + {}} handleClose={() => setAddCategoryOpen(!addCategoryOpen)} /> + + setEditCategoryOpen(!editCategoryOpen)} + data={currentCategory!} + /> + + setOpenConfirm(false)} + onConfirm={handleDelete} + isLoading={isDeleting} + title='Delete Category' + message='Are you sure you want to delete this category? This action cannot be undone.' + /> ) } diff --git a/src/views/apps/ecommerce/products/list/ProductListTable.tsx b/src/views/apps/ecommerce/products/list/ProductListTable.tsx index 6816cc6..1152afa 100644 --- a/src/views/apps/ecommerce/products/list/ProductListTable.tsx +++ b/src/views/apps/ecommerce/products/list/ProductListTable.tsx @@ -45,6 +45,8 @@ import { Box, CircularProgress } from '@mui/material' import Loading from '../../../../../components/layout/shared/Loading' import { useProductsQuery } from '../../../../../services/queries/products' import { Product } from '../../../../../types/services/product' +import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete' +import { useProductsMutation } from '../../../../../services/mutations/products' declare module '@tanstack/table-core' { interface FilterFns { @@ -108,6 +110,8 @@ const ProductListTable = () => { const [rowSelection, setRowSelection] = useState({}) const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(10) + const [openConfirm, setOpenConfirm] = useState(false) + const [productId, setProductId] = useState('') // Hooks const { lang: locale } = useParams() @@ -118,6 +122,8 @@ const ProductListTable = () => { limit: pageSize }) + const { mutate: deleteProduct, isPending: isDeleting } = useProductsMutation.deleteProduct() + const products = data?.products ?? [] const totalCount = data?.total_count ?? 0 @@ -132,6 +138,12 @@ const ProductListTable = () => { setCurrentPage(0) // Reset to first page }, []) + const handleDelete = () => { + deleteProduct(productId, { + onSuccess: () => setOpenConfirm(false) + }) + } + const columns = useMemo[]>( () => [ { @@ -213,7 +225,10 @@ const ProductListTable = () => { header: 'Actions', cell: ({ row }) => (
- + { { text: 'Delete', icon: 'tabler-trash', - menuItemProps: { onClick: () => console.log('click') } + menuItemProps: { + onClick: () => { + setOpenConfirm(true) + setProductId(row.original.id) + } + } }, { text: 'Duplicate', icon: 'tabler-copy' } ]} @@ -397,6 +417,13 @@ const ProductListTable = () => { disabled={isLoading} /> + + setOpenConfirm(false)} + onConfirm={handleDelete} + isLoading={isDeleting} + /> ) } diff --git a/src/views/apps/stock/adjustment/AdjustmentStockDrawer.tsx b/src/views/apps/stock/adjustment/AdjustmentStockDrawer.tsx new file mode 100644 index 0000000..29b210a --- /dev/null +++ b/src/views/apps/stock/adjustment/AdjustmentStockDrawer.tsx @@ -0,0 +1,203 @@ +// React Imports +import { useMemo, 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 Typography from '@mui/material/Typography' + +// Third-party Imports + +// Type Imports + +// Components Imports +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 { useOutletsQuery } from '../../../../services/queries/outlets' +import { useProductsQuery } from '../../../../services/queries/products' +import { InventoryAdjustRequest } from '../../../../types/services/inventory' + +type Props = { + open: boolean + handleClose: () => void +} + +const AdjustmentStockDrawer = (props: Props) => { + // Props + const { open, handleClose } = props + + const { mutate: adjustInventory, isPending: isCreating } = useInventoriesMutation.adjustInventory() + + // States + const [productInput, setProductInput] = useState('') + const [productDebouncedInput] = useDebounce(productInput, 500) // debounce for better UX + const [outletInput, setOutletInput] = useState('') + const [outletDebouncedInput] = useDebounce(outletInput, 500) // debounce for better UX + const [formData, setFormData] = useState({ + product_id: '', + outlet_id: '', + delta: 0, + reason: '' + }) + + const { data: outlets, isLoading: outletsLoading } = useOutletsQuery.getOutlets({ + search: outletDebouncedInput + }) + const { data: products, isLoading } = useProductsQuery.getProducts({ + search: productDebouncedInput + }) + + const outletOptions = useMemo(() => outlets?.outlets || [], [outlets]) + const options = useMemo(() => products?.products || [], [products]) + + // Handle Form Submit + const handleFormSubmit = (e: any) => { + e.preventDefault() + + adjustInventory( + { ...formData, delta: Number(formData.delta) }, + { + onSuccess: () => { + handleReset() + } + } + ) + } + + const handleInputChange = (e: any) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }) + } + + // Handle Form Reset + const handleReset = () => { + handleClose() + setFormData({ + product_id: '', + outlet_id: '', + delta: 0, + reason: '' + }) + } + + return ( + +
+ Adjust Inventory + + + +
+ +
+
+ option.name} + value={outletOptions.find(p => p.id === formData.outlet_id) || null} + onInputChange={(event, newOutlettInput) => { + setOutletInput(newOutlettInput) + }} + onChange={(event, newValue) => { + setFormData({ + ...formData, + outlet_id: newValue?.id || '' + }) + }} + renderInput={params => ( + + {outletsLoading && } + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + option.name} + value={options.find(p => p.id === formData.product_id) || null} + onInputChange={(event, newProductInput) => { + setProductInput(newProductInput) + }} + onChange={(event, newValue) => { + setFormData({ + ...formData, + product_id: newValue?.id || '' + }) + }} + renderInput={params => ( + + {isLoading && } + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + + +
+ + +
+ +
+
+ ) +} + +export default AdjustmentStockDrawer diff --git a/src/views/apps/stock/adjustment/StockListTable.tsx b/src/views/apps/stock/adjustment/StockListTable.tsx new file mode 100644 index 0000000..1aa9cb1 --- /dev/null +++ b/src/views/apps/stock/adjustment/StockListTable.tsx @@ -0,0 +1,392 @@ +'use client' + +// React Imports +import { useCallback, useEffect, useMemo, useState } from 'react' + +// Next Imports +import { useParams } from 'next/navigation' + +// MUI Imports +import Button from '@mui/material/Button' +import Card from '@mui/material/Card' +import CardHeader from '@mui/material/CardHeader' +import Checkbox from '@mui/material/Checkbox' +import Chip from '@mui/material/Chip' +import Divider from '@mui/material/Divider' +import MenuItem from '@mui/material/MenuItem' +import TablePagination from '@mui/material/TablePagination' +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 TablePaginationComponent from '@components/TablePaginationComponent' +import CustomTextField from '@core/components/mui/TextField' +import OptionMenu from '@core/components/option-menu' + +// Util Imports + +// 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 { useInventoriesQuery } from '../../../../services/queries/inventories' +import { Inventory } from '../../../../types/services/inventory' +import AdjustmentStockDrawer from './AdjustmentStockDrawer' + +declare module '@tanstack/table-core' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank: RankingInfo + } +} + +type InventoryWithActionsType = Inventory & { + 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 StockListTable = () => { + const [rowSelection, setRowSelection] = useState({}) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [openConfirm, setOpenConfirm] = useState(false) + const [productId, setProductId] = useState('') + const [addInventoryOpen, setAddInventoryOpen] = useState(false) + + // Fetch products with pagination and search + const { data, isLoading, error, isFetching } = useInventoriesQuery.getInventories({ + page: currentPage, + limit: pageSize + }) + + const { mutate: deleteInventory, isPending: isDeleting } = useInventoriesMutation.deleteInventory() + + const inventories = data?.inventory ?? [] + 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(0) // Reset to first page + }, []) + + const handleDelete = () => { + deleteInventory(productId, { + onSuccess: () => setOpenConfirm(false) + }) + } + + const columns = useMemo[]>( + () => [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ) + }, + columnHelper.accessor('product_id', { + header: 'Product', + cell: ({ row }) => {row.original.product_id} + }), + columnHelper.accessor('quantity', { + header: 'Quantity', + cell: ({ row }) => {row.original.quantity} + }), + columnHelper.accessor('reorder_level', { + header: 'Reorder Level', + cell: ({ row }) => {row.original.reorder_level} + }), + columnHelper.accessor('is_low_stock', { + header: 'Status', + cell: ({ row }) => ( + + ) + }), + // columnHelper.accessor('actions', { + // header: 'Actions', + // cell: ({ row }) => ( + //
+ // { + // setOpenConfirm(true) + // setProductId(row.original.id) + // } + // } + // }, + // { text: 'Duplicate', icon: 'tabler-copy' } + // ]} + // /> + //
+ // ), + // enableSorting: false + // }) + ], + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) + + const table = useReactTable({ + data: inventories as Inventory[], + columns, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + rowSelection, + pagination: { + pageIndex: currentPage, // <= penting! + 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 ( + <> + + + {/* {}} productData={[]} /> */} + +
+ console.log(value)} + placeholder='Search Product' + className='max-sm:is-full' + /> +
+ table.setPageSize(Number(e.target.value))} + className='flex-auto is-[70px] max-sm:is-full' + > + 10 + 25 + 50 + + + +
+
+
+ {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} + /> +
+ + setAddInventoryOpen(!addInventoryOpen)} /> + + setOpenConfirm(false)} + onConfirm={handleDelete} + isLoading={isDeleting} + title='Delete Inventory' + message='Are you sure you want to delete this inventory? This action cannot be undone.' + /> + + ) +} + +export default StockListTable diff --git a/src/views/apps/stock/list/AddStockDrawer.tsx b/src/views/apps/stock/list/AddStockDrawer.tsx new file mode 100644 index 0000000..d8090d3 --- /dev/null +++ b/src/views/apps/stock/list/AddStockDrawer.tsx @@ -0,0 +1,191 @@ +// React Imports +import { useMemo, 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 Typography from '@mui/material/Typography' + +// Third-party Imports + +// Type Imports + +// Components Imports +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 { useOutletsQuery } from '../../../../services/queries/outlets' +import { useProductsQuery } from '../../../../services/queries/products' +import { InventoryRequest } from '../../../../types/services/inventory' + +type Props = { + open: boolean + handleClose: () => void +} + +const AddStockDrawer = (props: Props) => { + // Props + const { open, handleClose } = props + + const { mutate: createInventory, isPending: isCreating } = useInventoriesMutation.createInventory() + + // States + const [productInput, setProductInput] = useState('') + const [productDebouncedInput] = useDebounce(productInput, 500) // debounce for better UX + const [outletInput, setOutletInput] = useState('') + const [outletDebouncedInput] = useDebounce(outletInput, 500) // debounce for better UX + const [formData, setFormData] = useState({ + product_id: '', + outlet_id: '', + quantity: 0 + }) + + const { data: outlets, isLoading: outletsLoading } = useOutletsQuery.getOutlets({ + search: outletDebouncedInput + }) + const { data: products, isLoading } = useProductsQuery.getProducts({ + search: productDebouncedInput + }) + + const outletOptions = useMemo(() => outlets?.outlets || [], [outlets]) + const options = useMemo(() => products?.products || [], [products]) + + // Handle Form Submit + const handleFormSubmit = (e: any) => { + e.preventDefault() + + createInventory( + { ...formData, quantity: Number(formData.quantity) }, + { + onSuccess: () => { + handleReset() + } + } + ) + } + + const handleInputChange = (e: any) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }) + } + + // Handle Form Reset + const handleReset = () => { + handleClose() + setFormData({ + product_id: '', + outlet_id: '', + quantity: 0 + }) + } + + return ( + +
+ Add Inventory + + + +
+ +
+
+ option.name} + value={outletOptions.find(p => p.id === formData.outlet_id) || null} + onInputChange={(event, newOutlettInput) => { + setOutletInput(newOutlettInput) + }} + onChange={(event, newValue) => { + setFormData({ + ...formData, + outlet_id: newValue?.id || '' + }) + }} + renderInput={params => ( + + {outletsLoading && } + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + option.name} + value={options.find(p => p.id === formData.product_id) || null} + onInputChange={(event, newProductInput) => { + setProductInput(newProductInput) + }} + onChange={(event, newValue) => { + setFormData({ + ...formData, + product_id: newValue?.id || '' + }) + }} + renderInput={params => ( + + {isLoading && } + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + +
+ + +
+ +
+
+ ) +} + +export default AddStockDrawer diff --git a/src/views/apps/stock/list/StockListTable.tsx b/src/views/apps/stock/list/StockListTable.tsx new file mode 100644 index 0000000..c726e30 --- /dev/null +++ b/src/views/apps/stock/list/StockListTable.tsx @@ -0,0 +1,392 @@ +'use client' + +// React Imports +import { useCallback, useEffect, useMemo, useState } from 'react' + +// Next Imports +import { useParams } from 'next/navigation' + +// MUI Imports +import Button from '@mui/material/Button' +import Card from '@mui/material/Card' +import CardHeader from '@mui/material/CardHeader' +import Checkbox from '@mui/material/Checkbox' +import Chip from '@mui/material/Chip' +import Divider from '@mui/material/Divider' +import MenuItem from '@mui/material/MenuItem' +import TablePagination from '@mui/material/TablePagination' +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 TablePaginationComponent from '@components/TablePaginationComponent' +import CustomTextField from '@core/components/mui/TextField' +import OptionMenu from '@core/components/option-menu' + +// Util Imports + +// 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 { useInventoriesQuery } from '../../../../services/queries/inventories' +import { Inventory } from '../../../../types/services/inventory' +import AddStockDrawer from './AddStockDrawer' + +declare module '@tanstack/table-core' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank: RankingInfo + } +} + +type InventoryWithActionsType = Inventory & { + 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 StockListTable = () => { + const [rowSelection, setRowSelection] = useState({}) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [openConfirm, setOpenConfirm] = useState(false) + const [productId, setProductId] = useState('') + const [addInventoryOpen, setAddInventoryOpen] = useState(false) + + // Fetch products with pagination and search + const { data, isLoading, error, isFetching } = useInventoriesQuery.getInventories({ + page: currentPage, + limit: pageSize + }) + + const { mutate: deleteInventory, isPending: isDeleting } = useInventoriesMutation.deleteInventory() + + const inventories = data?.inventory ?? [] + 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(0) // Reset to first page + }, []) + + const handleDelete = () => { + deleteInventory(productId, { + onSuccess: () => setOpenConfirm(false) + }) + } + + const columns = useMemo[]>( + () => [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ) + }, + columnHelper.accessor('product_id', { + header: 'Product', + cell: ({ row }) => {row.original.product_id} + }), + columnHelper.accessor('quantity', { + header: 'Quantity', + cell: ({ row }) => {row.original.quantity} + }), + columnHelper.accessor('reorder_level', { + header: 'Reorder Level', + cell: ({ row }) => {row.original.reorder_level} + }), + columnHelper.accessor('is_low_stock', { + header: 'Status', + cell: ({ row }) => ( + + ) + }), + columnHelper.accessor('actions', { + header: 'Actions', + cell: ({ row }) => ( +
+ { + setOpenConfirm(true) + setProductId(row.original.id) + } + } + }, + { text: 'Duplicate', icon: 'tabler-copy' } + ]} + /> +
+ ), + enableSorting: false + }) + ], + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) + + const table = useReactTable({ + data: inventories as Inventory[], + columns, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + rowSelection, + pagination: { + pageIndex: currentPage, // <= penting! + 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 ( + <> + + + {/* {}} productData={[]} /> */} + +
+ console.log(value)} + placeholder='Search Product' + className='max-sm:is-full' + /> +
+ table.setPageSize(Number(e.target.value))} + className='flex-auto is-[70px] max-sm:is-full' + > + 10 + 25 + 50 + + + +
+
+
+ {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} + /> +
+ + setAddInventoryOpen(!addInventoryOpen)} /> + + setOpenConfirm(false)} + onConfirm={handleDelete} + isLoading={isDeleting} + title='Delete Inventory' + message='Are you sure you want to delete this inventory? This action cannot be undone.' + /> + + ) +} + +export default StockListTable diff --git a/src/views/apps/stock/list/TableFilters.tsx b/src/views/apps/stock/list/TableFilters.tsx new file mode 100644 index 0000000..fec459d --- /dev/null +++ b/src/views/apps/stock/list/TableFilters.tsx @@ -0,0 +1,107 @@ +// React Imports +import { useState, useEffect } from 'react' + +// MUI Imports +import Grid from '@mui/material/Grid2' +import CardContent from '@mui/material/CardContent' +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' + +type ProductStockType = { [key: string]: boolean } + +// Vars +const productStockObj: ProductStockType = { + 'In Stock': true, + 'Out of Stock': false +} + +const TableFilters = ({ setData, productData }: { setData: (data: Product[]) => void; productData?: Product[] }) => { + // States + const [category, setCategory] = useState('') + const [stock, setStock] = useState('') + const [status, setStatus] = useState('') + + useEffect( + () => { + const filteredData = productData?.filter(product => { + if (category && product.category_id !== category) return false + if (stock && product.name !== stock) return false + if (status && product.name !== status) return false + + return true + }) + + setData(filteredData ?? []) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [category, stock, status, productData] + ) + + return ( + + + + setStatus(e.target.value)} + slotProps={{ + select: { displayEmpty: true } + }} + > + Select Status + Scheduled + Publish + Inactive + + + + setCategory(e.target.value)} + slotProps={{ + select: { displayEmpty: true } + }} + > + Select Category + Accessories + Home Decor + Electronics + Shoes + Office + Games + + + + setStock(e.target.value as string)} + slotProps={{ + select: { displayEmpty: true } + }} + > + Select Stock + In Stock + Out of Stock + + + + + ) +} + +export default TableFilters