diff --git a/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/[id]/details/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/[id]/details/page.tsx new file mode 100644 index 0000000..2a63669 --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/[id]/details/page.tsx @@ -0,0 +1,31 @@ +// Next Imports + +// Type Imports + +// Component Imports +import OrderDetails from '@views/apps/ecommerce/orders/details' + +/** + * ! 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 OrderDetailsPage = async () => { + + return +} + +export default OrderDetailsPage diff --git a/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/detail/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/ecommerce/orders/detail/page.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/[lang]/(dashboard)/(private)/apps/user/list/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/user/list/page.tsx index 6ac873d..a26eb1d 100644 --- a/src/app/[lang]/(dashboard)/(private)/apps/user/list/page.tsx +++ b/src/app/[lang]/(dashboard)/(private)/apps/user/list/page.tsx @@ -21,7 +21,7 @@ import UserList from '@views/apps/user/list' const UserListApp = async () => { - return + return } export default UserListApp diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/profit-loss/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/profit-loss/page.tsx index eba1a15..6af19a7 100644 --- a/src/app/[lang]/(dashboard)/(private)/dashboards/profit-loss/page.tsx +++ b/src/app/[lang]/(dashboard)/(private)/dashboards/profit-loss/page.tsx @@ -5,15 +5,13 @@ import Grid from '@mui/material/Grid2' // Component Imports import DistributedBarChartOrder from '@views/dashboards/crm/DistributedBarChartOrder' -import EarningReportsWithTabs from '@views/dashboards/crm/EarningReportsWithTabs' // Server Action Imports import Loading from '../../../../../../components/layout/shared/Loading' import { useProfitLossAnalytics } from '../../../../../../services/queries/analytics' -import { - ProductDataReport, - ProfitLossReport -} from '../../../../../../types/services/analytic' +import { DailyData, ProductDataReport, ProfitLossReport } from '../../../../../../types/services/analytic' +import EarningReportsWithTabs from '../../../../../../views/dashboards/crm/EarningReportsWithTabs' +import MultipleSeries from '../../../../../../views/dashboards/profit-loss/EarningReportWithTabs' function formatMetricName(metric: string): string { const nameMap: { [key: string]: string } = { @@ -41,7 +39,7 @@ const DashboardProfitLoss = () => { }) } - const metrics = ['revenue', 'cost', 'gross_profit', 'net_profit'] + const metrics = ['cost', 'revenue', 'gross_profit', 'net_profit'] const transformSalesData = (data: ProfitLossReport) => { return [ @@ -50,16 +48,30 @@ const DashboardProfitLoss = () => { avatarIcon: 'tabler-package', date: data.product_data.map((d: ProductDataReport) => d.product_name), series: [{ data: data.product_data.map((d: ProductDataReport) => d.revenue) }] - }, - // { - // type: 'profits', - // avatarIcon: 'tabler-currency-dollar', - // date: data.data.map((d: DailyData) => formatDate(d.date)), - // series: metrics.map(metric => ({ - // name: formatMetricName(metric as string), - // data: data.data.map((item: any) => item[metric] as number) - // })) - // } + } + // { + // type: 'profits', + // avatarIcon: 'tabler-currency-dollar', + // date: data.data.map((d: DailyData) => formatDate(d.date)), + // series: metrics.map(metric => ({ + // name: formatMetricName(metric as string), + // data: data.data.map((item: any) => item[metric] as number) + // })) + // } + ] + } + + const transformMultipleData = (data: ProfitLossReport) => { + return [ + { + type: 'profits', + avatarIcon: 'tabler-currency-dollar', + date: data.data.map((d: DailyData) => formatDate(d.date)), + series: metrics.map(metric => ({ + name: formatMetricName(metric as string), + data: data.data.map((item: any) => item[metric] as number) + })) + } ] } @@ -110,6 +122,9 @@ const DashboardProfitLoss = () => { + + + ) } diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx index 609adb9..54b83b3 100644 --- a/src/components/layout/vertical/VerticalMenu.tsx +++ b/src/components/layout/vertical/VerticalMenu.tsx @@ -109,6 +109,7 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => { {dictionary['navigation'].list} + {dictionary['navigation'].details} {dictionary['navigation'].list} diff --git a/src/redux-store/index.ts b/src/redux-store/index.ts index 6421eaa..37c577c 100644 --- a/src/redux-store/index.ts +++ b/src/redux-store/index.ts @@ -5,13 +5,15 @@ import productReducer from '@/redux-store/slices/product' import customerReducer from '@/redux-store/slices/customer' import paymentMethodReducer from '@/redux-store/slices/paymentMethod' import ingredientReducer from '@/redux-store/slices/ingredient' +import orderReducer from '@/redux-store/slices/order' export const store = configureStore({ reducer: { productReducer, customerReducer, paymentMethodReducer, - ingredientReducer + ingredientReducer, + orderReducer }, middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false }) }) diff --git a/src/redux-store/slices/order.ts b/src/redux-store/slices/order.ts new file mode 100644 index 0000000..d3f6f50 --- /dev/null +++ b/src/redux-store/slices/order.ts @@ -0,0 +1,64 @@ +// Third-party Imports +import type { PayloadAction } from '@reduxjs/toolkit' +import { createSlice } from '@reduxjs/toolkit' + +// Type Imports + +// Data Imports +import { Order } from '../../types/services/order' + +const initialState: { currentOrder: Order } = { + currentOrder: { + id: '', + order_number: '', + outlet_id: '', + user_id: '', + table_number: '', + order_type: '', + status: '', + subtotal: 0, + tax_amount: 0, + discount_amount: 0, + total_amount: 0, + total_cost: 0, + remaining_amount: 0, + payment_status: '', + refund_amount: 0, + is_void: false, + is_refund: false, + notes: '', + metadata: { + customer_name: '', + last_split_amount: 0, + last_split_customer_id: '', + last_split_customer_name: '', + last_split_payment_id: '', + last_split_quantities: {}, + last_split_type: '' + }, + created_at: '', + updated_at: '', + order_items: [], + payments: [], + total_paid: 0, + payment_count: 0, + split_type: '' + } +} + +export const orderSlice = createSlice({ + name: 'order', + initialState, + reducers: { + setOrder: (state, action: PayloadAction) => { + state.currentOrder = action.payload + }, + resetOrder: state => { + state.currentOrder = initialState.currentOrder + } + } +}) + +export const { setOrder, resetOrder } = orderSlice.actions + +export default orderSlice.reducer diff --git a/src/services/mutations/users.ts b/src/services/mutations/users.ts index 6618122..ca790bb 100644 --- a/src/services/mutations/users.ts +++ b/src/services/mutations/users.ts @@ -1,27 +1,52 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' +import { CustomerRequest } from '../../types/services/customer' import { api } from '../api' -import { User } from '../../types/services/user' +import { toast } from 'react-toastify' -type CreateUserPayload = { - name: string - email: string -} - -const useUsersMutation = () => { +export const useCustomersMutation = () => { const queryClient = useQueryClient() - - const createUser = useMutation({ - mutationFn: async newUser => { - const response = await api.post('/users', newUser) + + const createCustomer = useMutation({ + mutationFn: async (newCustomer: CustomerRequest) => { + const response = await api.post('/customers', newCustomer) return response.data }, onSuccess: () => { - // Optional: refetch 'users' list after success - queryClient.invalidateQueries({ queryKey: ['users'] }) + toast.success('Customer created successfully!') + queryClient.invalidateQueries({ queryKey: ['customers'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') } }) - return { createUser } -} + const updateCustomer = useMutation({ + mutationFn: async ({ id, payload }: { id: string; payload: CustomerRequest }) => { + const response = await api.put(`/customers/${id}`, payload) + return response.data + }, + onSuccess: () => { + toast.success('Customer updated successfully!') + queryClient.invalidateQueries({ queryKey: ['customers'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed') + } + }) -export default useUsersMutation + const deleteCustomer = useMutation({ + mutationFn: async (id: string) => { + const response = await api.delete(`/customers/${id}`) + return response.data + }, + onSuccess: () => { + toast.success('Customer deleted successfully!') + queryClient.invalidateQueries({ queryKey: ['customers'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed') + } + }) + + return { createCustomer, updateCustomer, deleteCustomer } +} diff --git a/src/services/queries/orders.ts b/src/services/queries/orders.ts index f88d7bb..ecd5018 100644 --- a/src/services/queries/orders.ts +++ b/src/services/queries/orders.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query' -import { Orders } from '../../types/services/order' +import { Order, Orders } from '../../types/services/order' import { api } from '../api' interface OrdersQueryParams { @@ -34,3 +34,13 @@ export function useOrders(params: OrdersQueryParams = {}) { } }) } + +export function useOrder(id: string) { + return useQuery({ + queryKey: ['orders', id], + queryFn: async () => { + const res = await api.get(`/orders/${id}`) + return res.data.data + } + }) +} diff --git a/src/services/queries/users.ts b/src/services/queries/users.ts index 0bf6510..e6b67dc 100644 --- a/src/services/queries/users.ts +++ b/src/services/queries/users.ts @@ -1,14 +1,36 @@ import { useQuery } from '@tanstack/react-query' +import { Users } from '../../types/services/user' import { api } from '../api' -import { User } from '../../types/services/user' +interface UsersQueryParams { + page?: number + limit?: number + search?: string +} -export function useUsers() { - return useQuery({ - queryKey: ['users'], +export function useUsers(params: UsersQueryParams = {}) { + const { page = 1, limit = 10, search = '', ...filters } = params + + return useQuery({ + queryKey: ['users', { page, limit, search, ...filters }], queryFn: async () => { - const res = await api.get('/users') - return res.data - }, + 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(`/users?${queryParams.toString()}`) + return res.data.data + } }) } diff --git a/src/types/services/order.ts b/src/types/services/order.ts index ede701e..9dddadb 100644 --- a/src/types/services/order.ts +++ b/src/types/services/order.ts @@ -1,12 +1,72 @@ -export interface Orders { - orders: Order[] - total_count: number - page: number - limit: number - total_pages: number +export type LastSplitQuantity = { + quantity: number + total_amount: number + unit_price: number } -export interface Order { +export type OrderMetadata = { + customer_name: string + last_split_amount: number + last_split_customer_id: string + last_split_customer_name: string + last_split_payment_id: string + last_split_quantities: Record + last_split_type: string +} + +export type OrderItem = { + id: string + order_id: string + product_id: string + product_name: string + product_variant_id: string | null + quantity: number + unit_price: number + total_price: number + modifiers: unknown[] // Adjust if modifiers have a defined structure + notes: string + status: string + created_at: string + updated_at: string + printer_type: string + paid_quantity: number +} + +export type PaymentOrderItem = { + id: string + payment_id: string + order_item_id: string + amount: number + created_at: string + updated_at: string +} + +export type PaymentMetadata = { + customer_id: string + customer_name: string + split_type: string +} + +export type Payment = { + id: string + order_id: string + payment_method_id: string + payment_method_name: string + payment_method_type: string + amount: number + status: string + split_number: number + split_total: number + split_type: string + split_description: string + refund_amount: number + metadata: PaymentMetadata + created_at: string + updated_at: string + payment_order_items: PaymentOrderItem[] +} + +export type Order = { id: string order_number: string outlet_id: string @@ -18,28 +78,27 @@ export interface Order { tax_amount: number discount_amount: number total_amount: number + total_cost: number + remaining_amount: number + payment_status: string + refund_amount: number + is_void: boolean + is_refund: boolean notes: string | null - metadata: { - customer_name: string - } + metadata: OrderMetadata created_at: string updated_at: string order_items: OrderItem[] + payments: Payment[] + total_paid: number + payment_count: number + split_type: string } -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: string - created_at: string - updated_at: string +export type Orders = { + orders: Order[], + total_count: number + page: number + limit: number + total_pages: number } diff --git a/src/types/services/user.ts b/src/types/services/user.ts index 437e494..4d0e2fe 100644 --- a/src/types/services/user.ts +++ b/src/types/services/user.ts @@ -1,5 +1,35 @@ export type User = { - id: string - name: string - email: string + id: string; + organization_id: string; + outlet_id: string; + name: string; + email: string; + role: string; + permissions: Record; + is_active: boolean; + created_at: string; // ISO date string + updated_at: string; // ISO date string +}; + +type Pagination = { + total_count: number; + page: number; + limit: number; + total_pages: number; +}; + +export type Users = { + users: User[]; + pagination: Pagination; +}; + +export type UserRequest = { + organization_id: string; + outlet_id: string; + name: string; + email: string; + password: string; + role: string; + permissions: Record; + is_active: boolean; } diff --git a/src/views/apps/ecommerce/orders/details/BillingAddressCard.tsx b/src/views/apps/ecommerce/orders/details/BillingAddressCard.tsx index a1feceb..db8ad92 100644 --- a/src/views/apps/ecommerce/orders/details/BillingAddressCard.tsx +++ b/src/views/apps/ecommerce/orders/details/BillingAddressCard.tsx @@ -2,64 +2,79 @@ import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' import Typography from '@mui/material/Typography' -import type { TypographyProps } from '@mui/material/Typography' // Type Imports import type { ThemeColor } from '@core/types' +import classnames from 'classnames' // Component Imports -import AddAddress from '@components/dialogs/add-edit-address' -import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick' +import CustomAvatar from '../../../../../@core/components/mui/Avatar' +import { Order } from '../../../../../types/services/order' +import { formatCurrency } from '../../../../../utils/transform' -// Vars -const data = { - firstName: 'Roker', - lastName: 'Terrace', - email: 'sbaser0@boston.com', - country: 'UK', - address1: 'Latheronwheel', - address2: 'KW5 8NW, London', - landmark: 'Near Water Plant', - city: 'London', - state: 'Capholim', - zipCode: '403114', - taxId: 'TAX-875623', - vatNumber: 'SDF754K77', - contact: '+1 (609) 972-22-22' +type PayementStatusType = { + text: string + color: ThemeColor + colorClassName: string } -const BillingAddress = () => { - // Vars - const typographyProps = (children: string, color: ThemeColor, className: string): TypographyProps => ({ - children, - color, - className - }) +const statusChipColor: { [key: string]: PayementStatusType } = { + pending: { + color: 'warning', + text: 'Pending', + colorClassName: 'text-warning' + }, + completed: { + color: 'success', + text: 'Paid', + colorClassName: 'text-success' + }, + cancelled: { + color: 'error', + text: 'Cancelled', + colorClassName: 'text-error' + } +} +const BillingAddress = ({ data }: { data: Order }) => { return (
- Billing Address - -
-
- 45 Roker Terrace - Latheronwheel - KW5 8NW, London - UK + + Payment Details ({data.payments.length} {data.payments.length === 1 ? 'Payment' : 'Payments'}) +
-
- Mastercard - Card Number: ******4291 -
+ {data.payments.map((payment, index) => ( +
+
+ + + +
+
+ {payment.payment_method_name} +
+ + + {statusChipColor[payment.status].text} + +
+
+ + {formatCurrency(payment.amount)} + +
+
+
+ ))}
) diff --git a/src/views/apps/ecommerce/orders/details/CustomerDetailsCard.tsx b/src/views/apps/ecommerce/orders/details/CustomerDetailsCard.tsx index f1fe4ad..3a25b2d 100644 --- a/src/views/apps/ecommerce/orders/details/CustomerDetailsCard.tsx +++ b/src/views/apps/ecommerce/orders/details/CustomerDetailsCard.tsx @@ -1,21 +1,18 @@ // MUI Imports +import Avatar from '@mui/material/Avatar' import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' -import Avatar from '@mui/material/Avatar' import Typography from '@mui/material/Typography' -import type { TypographyProps } from '@mui/material/Typography' // Type Imports -import type { ThemeColor } from '@core/types' import type { OrderType } from '@/types/apps/ecommerceTypes' // Component Imports import CustomAvatar from '@core/components/mui/Avatar' -import EditUserInfo from '@components/dialogs/edit-user-info' -import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick' // Util Imports import { getInitials } from '@/utils/getInitials' +import { Order } from '../../../../../types/services/order' const getAvatar = (params: Pick) => { const { avatar, customer } = params @@ -42,25 +39,17 @@ const userData = { useAsBillingAddress: true } -const CustomerDetails = ({ orderData }: { orderData?: OrderType }) => { - // Vars - const typographyProps = (children: string, color: ThemeColor, className: string): TypographyProps => ({ - children, - color, - className - }) - +const CustomerDetails = ({ orderData }: { orderData?: Order }) => { return ( Customer details
- {getAvatar({ avatar: orderData?.avatar ?? '', customer: orderData?.customer ?? '' })} + {getAvatar({ avatar: '', customer: orderData?.metadata.customer_name ?? '' })}
- {orderData?.customer} + {orderData?.metadata.customer_name} - Customer ID: #47389
@@ -68,24 +57,9 @@ const CustomerDetails = ({ orderData }: { orderData?: OrderType }) => { - 12 Orders + {orderData?.order_items.length} {orderData?.order_items.length === 1 ? 'Order' : 'Orders'}
-
-
- - Contact info - - -
- Email: {orderData?.email} - Mobile: +1 (609) 972-22-22 -
) diff --git a/src/views/apps/ecommerce/orders/details/OrderDetailHeader.tsx b/src/views/apps/ecommerce/orders/details/OrderDetailHeader.tsx index da7d702..6f52a59 100644 --- a/src/views/apps/ecommerce/orders/details/OrderDetailHeader.tsx +++ b/src/views/apps/ecommerce/orders/details/OrderDetailHeader.tsx @@ -1,16 +1,17 @@ // MUI Imports +import type { ButtonProps } from '@mui/material/Button' import Button from '@mui/material/Button' import Chip from '@mui/material/Chip' import Typography from '@mui/material/Typography' -import type { ButtonProps } from '@mui/material/Button' // Type Imports import type { ThemeColor } from '@core/types' -import type { OrderType } from '@/types/apps/ecommerceTypes' // Component Imports import ConfirmationDialog from '@components/dialogs/confirmation-dialog' import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick' +import { Order } from '../../../../../types/services/order' +import { formatDate } from '../../../../../utils/transform' type PayementStatusType = { text: string @@ -22,32 +23,32 @@ type StatusChipColorType = { } export const paymentStatus: { [key: number]: PayementStatusType } = { - 1: { text: 'Paid', color: 'success' }, - 2: { text: 'Pending', color: 'warning' }, - 3: { text: 'Cancelled', color: 'secondary' }, - 4: { text: 'Failed', color: 'error' } + 1: { text: 'paid', color: 'success' }, + 2: { text: 'pending', color: 'warning' }, + 3: { text: 'cancelled', color: 'secondary' }, + 4: { text: 'failed', color: 'error' } } export const statusChipColor: { [key: string]: StatusChipColorType } = { - Delivered: { color: 'success' }, - 'Out for Delivery': { color: 'primary' }, - 'Ready to Pickup': { color: 'info' }, - Dispatched: { color: 'warning' } + 'pending': { color: 'warning' }, + 'completed': { color: 'success' }, + 'partial': { color: 'secondary' }, + 'cancelled': { color: 'error' } } -const OrderDetailHeader = ({ orderData, order }: { orderData?: OrderType; order: string }) => { +const OrderDetailHeader = ({ orderData }: { orderData?: Order }) => { // Vars const buttonProps = (children: string, color: ThemeColor, variant: ButtonProps['variant']): ButtonProps => ({ children, color, variant - }) + }) return (
- {`Order #${order}`} + {`Order #${orderData?.order_number}`}
- {`${new Date(orderData?.date ?? '').toDateString()}, ${orderData?.time} (ET)`} + {`${formatDate(orderData!.created_at)}`}
= (row, columnId, value, addMeta) => { // Rank the item @@ -47,57 +49,44 @@ const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { } type dataType = { - productName: string - productImage: string - brand: string - price: number + product_name: string + status: string + unit_price: number quantity: number - total: number + total_price: number } -const orderData: dataType[] = [ - { - productName: 'OnePlus 7 Pro', - productImage: '/images/apps/ecommerce/product-21.png', - brand: 'OnePluse', - price: 799, - quantity: 1, - total: 799 +type PayementStatusType = { + text: string + color: ThemeColor + colorClassName: string +} + +const statusChipColor: { [key: string]: PayementStatusType } = { + pending: { + color: 'warning', + text: 'Pending', + colorClassName: 'text-warning' }, - { - productName: 'Magic Mouse', - productImage: '/images/apps/ecommerce/product-22.png', - brand: 'Google', - price: 89, - quantity: 1, - total: 89 + paid: { + color: 'success', + text: 'Paid', + colorClassName: 'text-success' }, - { - productName: 'Wooden Chair', - productImage: '/images/apps/ecommerce/product-23.png', - brand: 'Insofar', - price: 289, - quantity: 2, - total: 578 - }, - { - productName: 'Air Jorden', - productImage: '/images/apps/ecommerce/product-24.png', - brand: 'Nike', - price: 299, - quantity: 2, - total: 598 + cancelled: { + color: 'error', + text: 'Cancelled', + colorClassName: 'text-error' } -] +} // Column Definitions const columnHelper = createColumnHelper() -const OrderTable = () => { +const OrderTable = ({ data }: { data: OrderItem[] }) => { // States const [rowSelection, setRowSelection] = useState({}) // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [data, setData] = useState(...[orderData]) const [globalFilter, setGlobalFilter] = useState('') const columns = useMemo[]>( @@ -124,31 +113,43 @@ const OrderTable = () => { /> ) }, - columnHelper.accessor('productName', { + columnHelper.accessor('product_name', { header: 'Product', cell: ({ row }) => (
- {row.original.productName}
- {row.original.productName} + {row.original.product_name} - {row.original.brand} +
+ + + {statusChipColor[row.original.status].text} + +
) }), - columnHelper.accessor('price', { + columnHelper.accessor('unit_price', { header: 'Price', - cell: ({ row }) => {`$${row.original.price}`} + cell: ({ row }) => {formatCurrency(row.original.unit_price)} }), columnHelper.accessor('quantity', { header: 'Qty', cell: ({ row }) => {`${row.original.quantity}`} }), - columnHelper.accessor('total', { + columnHelper.accessor('total_price', { header: 'Total', - cell: ({ row }) => {`$${row.original.total}`} + cell: ({ row }) => {formatCurrency(row.original.total_price)} }) ], // eslint-disable-next-line react-hooks/exhaustive-deps @@ -156,7 +157,7 @@ const OrderTable = () => { ) const table = useReactTable({ - data: data as dataType[], + data: data as OrderItem[], columns, filterFns: { fuzzy: fuzzyFilter @@ -243,18 +244,11 @@ const OrderTable = () => { ) } -const OrderDetailsCard = () => { +const OrderDetailsCard = ({ data }: { data: Order }) => { return ( - - Edit - - } - /> - + +
@@ -262,15 +256,15 @@ const OrderDetailsCard = () => { Subtotal: - $2,093 + {formatCurrency(data.subtotal)}
- Shipping Fee: + Discount - $2 + {formatCurrency(data.discount_amount)}
@@ -278,7 +272,7 @@ const OrderDetailsCard = () => { Tax: - $28 + {formatCurrency(data.tax_amount)}
@@ -286,7 +280,7 @@ const OrderDetailsCard = () => { Total: - $2113 + {formatCurrency(data.total_amount)}
diff --git a/src/views/apps/ecommerce/orders/details/index.tsx b/src/views/apps/ecommerce/orders/details/index.tsx index 529a931..efc8883 100644 --- a/src/views/apps/ecommerce/orders/details/index.tsx +++ b/src/views/apps/ecommerce/orders/details/index.tsx @@ -1,43 +1,59 @@ +'use client' + // MUI Imports import Grid from '@mui/material/Grid2' // Type Imports -import type { OrderType } from '@/types/apps/ecommerceTypes' // Component Imports +import { redirect, useParams } from 'next/navigation' +import Loading from '../../../../../components/layout/shared/Loading' +import { useOrder } from '../../../../../services/queries/orders' +import BillingAddress from './BillingAddressCard' +import CustomerDetails from './CustomerDetailsCard' import OrderDetailHeader from './OrderDetailHeader' import OrderDetailsCard from './OrderDetailsCard' -import ShippingActivity from './ShippingActivityCard' -import CustomerDetails from './CustomerDetailsCard' import ShippingAddress from './ShippingAddressCard' -import BillingAddress from './BillingAddressCard' -const OrderDetails = ({ orderData, order }: { orderData?: OrderType; order: string }) => { +const OrderDetails = () => { + + const params = useParams() + + const { data, isLoading } = useOrder(params.id as string) + + if (isLoading) { + return + } + + if (!data) { + redirect('not-found') + } + return ( - + - - - - + + {/* + + */} - + + {/* + + */} - - - - + diff --git a/src/views/apps/ecommerce/orders/list/OrderListTable.tsx b/src/views/apps/ecommerce/orders/list/OrderListTable.tsx index 6393944..574bd9b 100644 --- a/src/views/apps/ecommerce/orders/list/OrderListTable.tsx +++ b/src/views/apps/ecommerce/orders/list/OrderListTable.tsx @@ -215,7 +215,7 @@ const OrderListTable = () => { text: 'View', icon: 'tabler-eye', href: getLocalizedUrl( - `/apps/ecommerce/orders/details/${row.original.order_number}`, + `/apps/ecommerce/orders/${row.original.id}/details`, locale as Locale ), linkProps: { className: 'flex items-center gap-2 is-full plb-2 pli-4' } diff --git a/src/views/apps/user/list/AddUserDrawer.tsx b/src/views/apps/user/list/AddUserDrawer.tsx index dc819ff..4d909cc 100644 --- a/src/views/apps/user/list/AddUserDrawer.tsx +++ b/src/views/apps/user/list/AddUserDrawer.tsx @@ -3,94 +3,50 @@ 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' // Third-party Imports -import { useForm, Controller } from 'react-hook-form' +import { Controller, useForm } from 'react-hook-form' // Types Imports import type { UsersType } from '@/types/apps/userTypes' // Component Imports import CustomTextField from '@core/components/mui/TextField' +import { UserRequest } from '../../../../types/services/user' +import { Switch } from '@mui/material' type Props = { open: boolean handleClose: () => void - userData?: UsersType[] - setData: (data: UsersType[]) => void -} - -type FormValidateType = { - fullName: string - username: string - email: string - role: string - plan: string - status: string -} - -type FormNonValidateType = { - company: string - country: string - contact: string } // Vars const initialData = { - company: '', - country: '', - contact: '' + name: '', + email: '', + password: '', + role: '', + permissions: {}, + is_active: true, + organization_id: '', + outlet_id: '', } const AddUserDrawer = (props: Props) => { // Props - const { open, handleClose, userData, setData } = props + const { open, handleClose } = props // States - const [formData, setFormData] = useState(initialData) + const [formData, setFormData] = useState(initialData) - // Hooks - const { - control, - reset: resetForm, - handleSubmit, - formState: { errors } - } = useForm({ - defaultValues: { - fullName: '', - username: '', - email: '', - role: '', - plan: '', - status: '' - } - }) - - const onSubmit = (data: FormValidateType) => { - const newUser: UsersType = { - id: (userData?.length && userData?.length + 1) || 1, - avatar: `/images/avatars/${Math.floor(Math.random() * 8) + 1}.png`, - fullName: data.fullName, - username: data.username, - email: data.email, - role: data.role, - currentPlan: data.plan, - status: data.status, - company: formData.company, - country: formData.country, - contact: formData.contact, - billing: userData?.[Math.floor(Math.random() * 50) + 1].billing ?? 'Auto Debit' - } - - setData([...(userData ?? []), newUser]) + const onSubmit = () => { handleClose() setFormData(initialData) - resetForm({ fullName: '', username: '', email: '', role: '', plan: '', status: '' }) } const handleReset = () => { @@ -98,6 +54,13 @@ const AddUserDrawer = (props: Props) => { setFormData(initialData) } + const handleInputChange = (e: any) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }) + } + return ( {
-
onSubmit(data))} className='flex flex-col gap-6 p-6'> - ( - - )} + + - ( - - )} - /> - ( - - )} - /> - ( - - Admin - Author - Editor - Maintainer - Subscriber - - )} - /> - ( - - Basic - Company - Enterprise - Team - - )} - /> - ( - - Pending - Active - Inactive - - )} + value={formData.email} + onChange={handleInputChange} /> setFormData({ ...formData, company: e.target.value })} - /> - setFormData({ ...formData, country: e.target.value })} - label='Select Country' - slotProps={{ - htmlInput: { placeholder: 'Country' } - }} - > - India - USA - Australia - Germany - - setFormData({ ...formData, contact: e.target.value })} + type='password' + label='Password' + placeholder='********' + name='password' + value={formData.password} + onChange={handleInputChange} /> +
+
+ + Active + +
+ setFormData({ ...formData, is_active: e.target.checked })} + /> +
) }), - columnHelper.accessor('currentPlan', { - header: 'Plan', - cell: ({ row }) => ( - - {row.original.currentPlan} - - ) + columnHelper.accessor('email', { + header: 'Email', + cell: ({ row }) => {row.original.email} }), - columnHelper.accessor('billing', { - header: 'Billing', - cell: ({ row }) => {row.original.billing} - }), - columnHelper.accessor('status', { + columnHelper.accessor('is_active', { header: 'Status', cell: ({ row }) => (
) }), - columnHelper.accessor('action', { + columnHelper.accessor('actions', { header: 'Action', cell: ({ row }) => (
- setData(data?.filter(product => product.id !== row.original.id))}> + {}}> - - - - - { }) ], // eslint-disable-next-line react-hooks/exhaustive-deps - [data, filteredData] + [data] ) const table = useReactTable({ - data: filteredData as UsersType[], + data: users as User[], columns, filterFns: { fuzzy: fuzzyFilter }, state: { rowSelection, - globalFilter - }, - initialState: { pagination: { - pageSize: 10 + pageIndex: currentPage, + 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) => { @@ -309,25 +299,25 @@ const UserListTable = ({ tableData }: { tableData?: UsersType[] }) => { <> - + {/* */}
- table.setPageSize(Number(e.target.value))} - className='max-sm:is-full sm:is-[70px]' - > - 10 - 25 - 50 - + setSearch(value as string)} + placeholder='Search User' + className='max-sm:is-full' + />
- setGlobalFilter(String(value))} - placeholder='Search User' - className='max-sm:is-full' - /> + + 10 + 25 + 50 +
- - - {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) - }} - /> */} + + ( + + )} + count={totalCount} + rowsPerPage={pageSize} + page={currentPage} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + disabled={isLoading} + />
+ setAddUserOpen(!addUserOpen)} - userData={data} - setData={setData} /> ) diff --git a/src/views/apps/user/list/index.tsx b/src/views/apps/user/list/index.tsx index 7d94df5..1a51b80 100644 --- a/src/views/apps/user/list/index.tsx +++ b/src/views/apps/user/list/index.tsx @@ -6,16 +6,15 @@ import type { UsersType } from '@/types/apps/userTypes' // Component Imports import UserListTable from './UserListTable' -import UserListCards from './UserListCards' -const UserList = ({ userData }: { userData?: UsersType[] }) => { +const UserList = () => { return ( - + {/* - + */} - + ) diff --git a/src/views/dashboards/crm/EarningReportsWithTabs.tsx b/src/views/dashboards/crm/EarningReportsWithTabs.tsx index 4050c62..9e46212 100644 --- a/src/views/dashboards/crm/EarningReportsWithTabs.tsx +++ b/src/views/dashboards/crm/EarningReportsWithTabs.tsx @@ -175,7 +175,7 @@ const EarningReportsWithTabs = ({ data }: { data: TabType[] }) => { breakpoint: 1450, options: { plotOptions: { - bar: { columnWidth: '45%' } + bar: { columnWidth: '35%' } } } }, @@ -206,7 +206,7 @@ const EarningReportsWithTabs = ({ data }: { data: TabType[] }) => { return ( } /> diff --git a/src/views/dashboards/profit-loss/EarningReportWithTabs.tsx b/src/views/dashboards/profit-loss/EarningReportWithTabs.tsx new file mode 100644 index 0000000..edc9373 --- /dev/null +++ b/src/views/dashboards/profit-loss/EarningReportWithTabs.tsx @@ -0,0 +1,249 @@ +'use client' + +// React Imports +import type { SyntheticEvent } from 'react' +import { useState } from 'react' + +// Next Imports +import dynamic from 'next/dynamic' + +// MUI Imports +import TabContext from '@mui/lab/TabContext' +import TabList from '@mui/lab/TabList' +import TabPanel from '@mui/lab/TabPanel' +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import CardHeader from '@mui/material/CardHeader' +import Tab from '@mui/material/Tab' +import Typography from '@mui/material/Typography' +import type { Theme } from '@mui/material/styles' +import { useTheme } from '@mui/material/styles' + +// Third Party Imports +import type { ApexOptions } from 'apexcharts' +import classnames from 'classnames' + +// Components Imports +import CustomAvatar from '@core/components/mui/Avatar' +import OptionMenu from '@core/components/option-menu' +import Loading from '../../../components/layout/shared/Loading' +import { formatShortCurrency } from '../../../utils/transform' + +// Styled Component Imports +const AppReactApexCharts = dynamic(() => import('@/libs/styles/AppReactApexCharts')) + +type ApexChartSeries = NonNullable +type ApexChartSeriesData = Exclude + +type TabType = { + type: string + avatarIcon: string + date: any + series: ApexChartSeries +} + +const renderTabs = (tabData: TabType[], value: string) => { + return tabData.map((item, index) => ( + + + + + + {item.type} + +
+ } + /> + )) +} + +const renderTabPanels = (tabData: TabType[], theme: Theme, options: ApexOptions, colors: string[]) => { + return tabData.map((item, index) => { + const max = Math.max(...((item.series[0] as ApexChartSeriesData).data as number[])) + const seriesIndex = ((item.series[0] as ApexChartSeriesData).data as number[]).indexOf(max) + + const finalColors = colors.map((color, i) => (seriesIndex === i ? 'var(--mui-palette-primary-main)' : color)) + + return ( + + + + ) + }) +} + +const MultipleSeries = ({ data }: { data: TabType[] }) => { + // States + const [value, setValue] = useState(data[0].type) + + // Hooks + const theme = useTheme() + + // Vars + const disabledText = 'var(--mui-palette-text-disabled)' + + const handleChange = (event: SyntheticEvent, newValue: string) => { + setValue(newValue) + } + + const colors = Array(9).fill('var(--mui-palette-primary-lightOpacity)') + + const options: ApexOptions = { + chart: { + parentHeightOffset: 0, + toolbar: { show: false } + }, + plotOptions: { + bar: { + horizontal: false, + columnWidth: '55%', + borderRadius: 2, + borderRadiusApplication: 'end' + } + }, + legend: { show: false }, + tooltip: { enabled: true }, + dataLabels: { + enabled: false, + offsetY: -11 + // formatter: val => formatShortCurrency(Number(val)), + // style: { + // fontWeight: 500, + // colors: ['var(--mui-palette-text-primary)'], + // fontSize: theme.typography.body1.fontSize as string + // } + }, + colors: [ + 'var(--mui-palette-primary-main)', + 'var(--mui-palette-info-main)', + 'var(--mui-palette-warning-main)', + 'var(--mui-palette-success-main)' + ], + states: { + hover: { + filter: { type: 'none' } + }, + active: { + filter: { type: 'none' } + } + }, + stroke: { width: 5, colors: ['transparent'] }, + grid: { + show: true, + padding: { + top: -19, + left: -4, + right: 0, + bottom: -11 + } + }, + xaxis: { + axisTicks: { show: false }, + axisBorder: { color: 'var(--mui-palette-divider)' }, + categories: data.find(item => item.type === value)?.date, + tickPlacement: 'between', + labels: { + style: { + colors: disabledText, + fontFamily: theme.typography.fontFamily, + fontSize: theme.typography.body2.fontSize as string + } + } + }, + yaxis: { + labels: { + offsetX: -18, + formatter: val => `${formatShortCurrency(Number(val))}`, + style: { + colors: disabledText, + fontFamily: theme.typography.fontFamily, + fontSize: theme.typography.body2.fontSize as string + } + } + }, + responsive: [ + { + breakpoint: 1450, + options: { + plotOptions: { + bar: { columnWidth: '35%' } + } + } + }, + { + breakpoint: 600, + options: { + dataLabels: { + style: { + fontSize: theme.typography.body2.fontSize as string + } + }, + plotOptions: { + bar: { columnWidth: '58%' } + } + } + }, + { + breakpoint: 500, + options: { + plotOptions: { + bar: { columnWidth: '70%' } + } + } + } + ] + } + + return ( + + } + /> + + + {data.length > 1 && ( + + {renderTabs(data, value)} + + + + +
+ } + /> + + )} + {renderTabPanels(data, theme, options, colors)} + + + + ) +} + +export default MultipleSeries