diff --git a/.env.development b/.env.development index 0eeb323..58a088c 100644 --- a/.env.development +++ b/.env.development @@ -2,6 +2,6 @@ VITE_APP_NAME=tpay-dashboard-tl VITE_APP_VERSION=1=9.1.1 GENERATE_SOURCEMAP=false -VITE_APP_API_URL=https://tpay.shiblysolution.id/api/d +VITE_APP_API_URL=https://tpay.shiblysolution.id/api # VITE_APP_API_URL=http://127.0.0.1:4003/api VITE_ENV=development diff --git a/package-lock.json b/package-lock.json index f1ffce0..95dac4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@formatjs/intl-pluralrules": "^5.3.4", "@formatjs/intl-relativetimeformat": "^11.4.4", "@mui/base": "5.0.0-beta.40", + "@mui/icons-material": "^6.4.6", "@mui/material": "^6.1.6", "@mui/utils": "^6.1.6", "@radix-ui/react-avatar": "^1.1.1", @@ -2646,6 +2647,31 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.6.tgz", + "integrity": "sha512-rGJBvIQQbQAlyKYljHQ8wAQS/K2/uYwvemcpygnAmCizmCI4zSF9HQPuiG8Ql4YLZ6V/uKjA3WHIYmF/8sV+pQ==", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.4.6", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz", diff --git a/package.json b/package.json index 29c3f8b..551e853 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@formatjs/intl-pluralrules": "^5.3.4", "@formatjs/intl-relativetimeformat": "^11.4.4", "@mui/base": "5.0.0-beta.40", + "@mui/icons-material": "^6.4.6", "@mui/material": "^6.1.6", "@mui/utils": "^6.1.6", "@radix-ui/react-avatar": "^1.1.1", diff --git a/src/auth/pages/jwt/Login.tsx b/src/auth/pages/jwt/Login.tsx index df76465..a7824cc 100644 --- a/src/auth/pages/jwt/Login.tsx +++ b/src/auth/pages/jwt/Login.tsx @@ -41,12 +41,8 @@ const Login = () => { setLoading(true); try { - if (!login) { - throw new Error('JWTProvider is required for this form.'); - } - + if (!login) throw new Error('JWTProvider is required for this form.'); await login(values.username, values.password); - // console.log(login); if (values.remember) { localStorage.setItem('username', values.username); diff --git a/src/components/confirm.tsx b/src/components/confirm.tsx new file mode 100644 index 0000000..b3a5596 --- /dev/null +++ b/src/components/confirm.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import Button from "@mui/material/Button"; + +const ConfirmDialog = ({ open, onClose, title, content, onYes, onNo }: any) => { + return ( + + {title && {title}} + {content && {content}} + + + + + + ); +}; + +export default ConfirmDialog; \ No newline at end of file diff --git a/src/components/ui/DataTable.tsx b/src/components/ui/DataTable.tsx new file mode 100644 index 0000000..aab44c5 --- /dev/null +++ b/src/components/ui/DataTable.tsx @@ -0,0 +1,156 @@ +'use client'; + +import { + ColumnDef, + ColumnFiltersState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + SortingState, + useReactTable +} from '@tanstack/react-table'; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from '@/components/ui/table'; +import { useState } from 'react'; +import { Input } from './input'; +import { Button } from './button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger +} from './dialog'; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + onUpdate: any; + onDelete: any; +} + +export function DataTable({ + columns, + data, + createGroup, + onUpdate, + onDelete +}: DataTableProps & { createGroup?: () => void }) { + const [columnFilters, setColumnFilters] = useState([]); + const [sorting, setSorting] = useState([]); + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + onColumnFiltersChange: setColumnFilters, + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + state: { + sorting, + columnFilters + } + }); + + return ( +
+
+ table.getColumn('name')?.setFilterValue(event.target.value)} + className="max-w-sm" + /> + + {createGroup && ( + + )} +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + Actions + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + +
+ + +
+
+
+ )) + ) : ( + + + No results. + + + )} +
+
+
+
+ + +
+
+ ); +} diff --git a/src/config/api.config.ts b/src/config/api.config.ts index 07e6b58..25146a3 100644 --- a/src/config/api.config.ts +++ b/src/config/api.config.ts @@ -1,12 +1,16 @@ interface apiConfigProps { service_dashboard: string; + service_customer: string; service_master_data: string; } const API_URL = import.meta.env.VITE_APP_API_URL; const apiConfig: apiConfigProps = { - service_dashboard: `${API_URL}${import.meta.env.VITE_ENV != 'development' ? '/d' : ""}`, - service_master_data: `${API_URL}/m`, + // service_dashboard: `${API_URL}${import.meta.env.VITE_ENV != 'development' ? '/d' : ''}`, + service_dashboard: `${API_URL}/d`, + service_customer: `${API_URL}/c`, + // service_master_data: `${API_URL}/m` + service_master_data: `${API_URL}/t` }; export { apiConfig }; diff --git a/src/layouts/demo2/header/Header.tsx b/src/layouts/demo2/header/Header.tsx index fa2c522..474cf23 100644 --- a/src/layouts/demo2/header/Header.tsx +++ b/src/layouts/demo2/header/Header.tsx @@ -19,7 +19,7 @@ const Header = () => { return (
{ /> */} logo {
-

TPAY Dashboard Portal

+

TPAY Dashboard Portal

); diff --git a/src/layouts/demo2/navbar/Navbar.tsx b/src/layouts/demo2/navbar/Navbar.tsx index a114628..86b7a2a 100644 --- a/src/layouts/demo2/navbar/Navbar.tsx +++ b/src/layouts/demo2/navbar/Navbar.tsx @@ -3,7 +3,7 @@ import { NavbarMenu } from '../'; const Navbar = () => { return ( -
+
diff --git a/src/pages/access/access-type/AccessType.tsx b/src/pages/access/access-type/AccessType.tsx new file mode 100644 index 0000000..eaa656d --- /dev/null +++ b/src/pages/access/access-type/AccessType.tsx @@ -0,0 +1,21 @@ +import { Container, DataGridInner } from '@/components'; +import { + ManageAccessTypeContext, + ManageAccessTypeContextProvider +} from './hooks/ManageAccessTypeContext'; +import AddDialog from './blocks/AddDialog'; + +const AccessType = () => { + return ( + + +
+ +
+ +
+
+ ); +}; + +export default AccessType; diff --git a/src/pages/access/access-type/blocks/AddDialog.tsx b/src/pages/access/access-type/blocks/AddDialog.tsx new file mode 100644 index 0000000..609640c --- /dev/null +++ b/src/pages/access/access-type/blocks/AddDialog.tsx @@ -0,0 +1,141 @@ +import { apiConfig } from '@/config/api.config'; +import { useRef, useState } from 'react'; +import { useManageAccessTypeContext } from '../hooks/useManageAccessTypeContext'; +import { Alert, KeenIcon, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { Dialog, DialogBody, DialogContent, DialogHeader } from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_dashboard; + +const AddDialog = () => { + const parentRef = useRef(null); + const { showAddDialog, handleAddDialog, accessTypes } = useManageAccessTypeContext(); + const { reload } = useDataGrid(); + const { PostData, PutData } = useCallApi(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const initialState = { + name: '', + description: '', + internal_name: '' + }; + + const [formField, setFormField] = useState(initialState); + const resetForm = () => { + setFormField(initialState); + }; + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + // setIsSubmitting(true); + console.log(formField); + }; + + const handleReset = () => { + setFormField(initialState); + }; + + return ( + handleAddDialog(open)}> + + +
+
+

+ Create Access Type +

+
+
+
{ + handleAddDialog(false); + resetForm(); + }} + > + +
+
+
+ +
+ {alert.show && ( + +

{alert.message}

+
+ )} +
+
+
+
+ + + setFormField((prev) => ({ ...prev, name: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, description: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, internal_name: target.value })) + } + /> +
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/access/access-type/blocks/ListToolBar.tsx b/src/pages/access/access-type/blocks/ListToolBar.tsx new file mode 100644 index 0000000..b99d8e1 --- /dev/null +++ b/src/pages/access/access-type/blocks/ListToolBar.tsx @@ -0,0 +1,49 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useManageAccessTypeContext } from '../hooks/useManageAccessTypeContext'; +import { Button } from '@/components/ui/button'; + +const ListToolbar = () => { + const { table, reload } = useDataGrid(); + const { handleAddDialog, handleEditDialog } = useManageAccessTypeContext(); + + return ( +
+
+
+
+ + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/access/access-type/hooks/ManageAccessTypeContext.tsx b/src/pages/access/access-type/hooks/ManageAccessTypeContext.tsx new file mode 100644 index 0000000..546d226 --- /dev/null +++ b/src/pages/access/access-type/hooks/ManageAccessTypeContext.tsx @@ -0,0 +1,162 @@ +import { DataGridColumnHeader, DataGridProvider } from '@/components'; +import { Toaster } from '@/components/ui/sonner'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { ColumnDef } from '@tanstack/react-table'; +import { createContext, useCallback, useMemo, useState } from 'react'; +import ListToolbar from '../blocks/ListToolBar'; + +interface SelectedUser { + id: string; + name: string; + internal_name: string; + description: string; +} + +interface AccessTypeProps { + id: string; + name: string; + internal_name: string; + description: string; +} + +interface ContextProps { + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_user: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + selectedUser: string | null; + accessTypes: AccessTypeProps[]; +} + +const initialProps: ContextProps = { + showEditDialog: false, + handleEditDialog: () => {}, + showAddDialog: false, + handleAddDialog: () => {}, + selectedUser: null, + accessTypes: [] +}; + +const ManageAccessTypeContext = createContext(initialProps); +const API_URL = apiConfig.service_dashboard; + +const ManageAccessTypeContextProvider = ({ children }: { children: React.ReactNode }) => { + const [showEditDialog, setShowEditDialog] = useState(false); + const [showAddDialog, setShowAddDialog] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [accessTypes, setAccessTypes] = useState([]); + const { GetData } = useCallApi(); + + const handleEditDialog = useCallback((show: boolean, selected_user: string | null) => { + setSelectedUser(show ? selected_user : null); + setShowEditDialog(show); + }, []); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.internal_name, + id: 'internal_name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.description, + id: 'description', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + id: 'actions', + enableSorting: false, + header: ({ column }) => , + cell: ({ row }) => { + return ( + <> + + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleAddDialog, handleEditDialog] + ); + + return ( +
+
+

Manage Access Type

+
+ + + } + sorting={[{ id: 'id', desc: true }]} + serverSide={true} + > + {children} + + +
+ ); +}; + +export { ManageAccessTypeContext, ManageAccessTypeContextProvider }; +export type { SelectedUser }; diff --git a/src/pages/access/access-type/hooks/useManageAccessTypeContext.tsx b/src/pages/access/access-type/hooks/useManageAccessTypeContext.tsx new file mode 100644 index 0000000..2113167 --- /dev/null +++ b/src/pages/access/access-type/hooks/useManageAccessTypeContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageAccessTypeContext } from './ManageAccessTypeContext'; + +const useManageAccessTypeContext = () => { + const context = useContext(ManageAccessTypeContext); + + if (!context) throw new Error('useManageAccessTypeContext must be used within AuthProvider'); + + return context; +}; + +export { useManageAccessTypeContext }; diff --git a/src/pages/access/member-credentials/MemberCredentials.tsx b/src/pages/access/member-credentials/MemberCredentials.tsx new file mode 100644 index 0000000..2ef4cfc --- /dev/null +++ b/src/pages/access/member-credentials/MemberCredentials.tsx @@ -0,0 +1,21 @@ +import MemberCredentialsForm from './blocks/MemberCredentialsForm'; +import { useMemberCredentials } from './hooks'; + +const MemberCredentials = () => { + const { error, memberCredentials, handleChange, handleSubmit } = useMemberCredentials(); + return ( +
+
+

Member Credential

+ +
+
+ ); +}; + +export default MemberCredentials; diff --git a/src/pages/access/member-credentials/blocks/MemberCredentialsForm.tsx b/src/pages/access/member-credentials/blocks/MemberCredentialsForm.tsx new file mode 100644 index 0000000..1f066f3 --- /dev/null +++ b/src/pages/access/member-credentials/blocks/MemberCredentialsForm.tsx @@ -0,0 +1,97 @@ +import { Alert } from '@/components'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; + +interface MemberCredentials { + accessType: string | null; + username: string; + credential: string; + confirmCredential: string; +} + +interface Props { + form: MemberCredentials; + error: string | null; + onChange: (key: keyof MemberCredentials, value: string) => void; + onSubmit: () => void; +} + +const MemberCredentialsForm = ({ form, error, onChange, onSubmit }: Props) => { + return ( +
+

Create Member Credentials

+ + {error && ( + + {error} + + )} + +
+ {/* Access Type */} +
+ + +
+ + {/* Username */} +
+ + onChange('username', e.target.value)} /> +
+ + {/* Credentials */} +
+ + onChange('credential', e.target.value)} + /> +
+ + {/* Confirm Credentials */} +
+ + onChange('confirmCredential', e.target.value)} + /> +
+ + +
+
+ ); +}; + +export default MemberCredentialsForm; diff --git a/src/pages/access/member-credentials/hooks/index.ts b/src/pages/access/member-credentials/hooks/index.ts new file mode 100644 index 0000000..e3b3fc4 --- /dev/null +++ b/src/pages/access/member-credentials/hooks/index.ts @@ -0,0 +1 @@ +export * from './useMemberCredentials'; diff --git a/src/pages/access/member-credentials/hooks/useMemberCredentials.tsx b/src/pages/access/member-credentials/hooks/useMemberCredentials.tsx new file mode 100644 index 0000000..4cfd302 --- /dev/null +++ b/src/pages/access/member-credentials/hooks/useMemberCredentials.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; + +type AccessType = 'Pin Credentials' | 'Secret Auth' | 'APIKey' | 'Web Credentials' | 'OTM Tpay'; + +interface MemberCredentials { + accessType: AccessType | null; + username: string; + credential: string; + confirmCredential: string; +} + +export const useMemberCredentials = () => { + const [memberCredentials, setMemberCredentials] = useState({ + accessType: null, + username: '', + credential: '', + confirmCredential: '' + }); + const [error, setError] = useState(null); + + const handleChange = (key: keyof MemberCredentials, value: string) => { + setMemberCredentials((prevCredentials) => ({ ...prevCredentials, [key]: value })); + }; + + const handleSubmit = () => { + setError(null); + + if (memberCredentials.username === '' || memberCredentials.credential === '') { + setError('Please fill in all fields'); + return; + } + + if (memberCredentials.credential !== memberCredentials.confirmCredential) { + setError('Credentials do not match'); + return; + } + + console.log('Data Submitted: ', memberCredentials); + }; + + return { memberCredentials, handleChange, handleSubmit, error }; +}; diff --git a/src/pages/account/manage-account/Columns.tsx b/src/pages/account/manage-account/Columns.tsx new file mode 100644 index 0000000..64fc8fa --- /dev/null +++ b/src/pages/account/manage-account/Columns.tsx @@ -0,0 +1,71 @@ +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '@/components/ui/dropdown-menu'; +import { ColumnDef } from '@tanstack/react-table'; +import { MoreHorizontal } from 'lucide-react'; + +export type Account = { + createdDate: Date; + creditLimit: number | null; + currency: string | null; + description: string; + id: number; + name: string; + systemAccount: boolean; +}; + +export const columns: ColumnDef[] = [ + { + accessorKey: 'id', + header: 'ID' + }, + { + accessorKey: 'name', + header: 'Name' + }, + { + accessorKey: 'description', + header: 'Description' + }, + { + accessorKey: 'systemAccount', + header: 'System Account' + }, + { + accessorKey: 'createdDate', + header: 'Created Date', + cell: ({ row }) => new Date(row.original.createdDate).toLocaleDateString() + }, + { + id: 'actions', + cell: ({ row }) => { + const dataAccount = row.original; + + return ( + + + + + + Actions + navigator.clipboard.writeText(dataAccount.id.toString())} + > + Copy account ID + + {/* */} + + + ); + } + } +]; diff --git a/src/pages/account/manage-account/ManageAccount.tsx b/src/pages/account/manage-account/ManageAccount.tsx new file mode 100644 index 0000000..b2a5f31 --- /dev/null +++ b/src/pages/account/manage-account/ManageAccount.tsx @@ -0,0 +1,83 @@ +import { apiConfig } from '@/config/api.config'; +import { Account, columns } from './Columns'; +import { DataTable } from '@/components/ui/DataTable'; +import axios from 'axios'; +import { useEffect, useState } from 'react'; +import { getData } from '@/utils'; +import { useCallApi } from '@/hooks'; + +const API_URL = apiConfig.service_dashboard; + +const dataAccount: Account[] = [ + { + id: 1, + name: 'eMoney Account', + description: 'Rekening Member eMoney', + systemAccount: false, + createdDate: new Date('2019-12-05'), + creditLimit: null, + currency: null + }, + { + id: 2, + name: 'Merchant Account', + description: 'Rekening Merchant', + systemAccount: false, + createdDate: new Date(), + creditLimit: null, + currency: null + } +]; + +const ManageAccount = () => { + const [accounts, setAccounts] = useState([]); + const { GetData } = useCallApi(); + // console.log(accounts); + + useEffect(() => { + const fetchAccount = async () => { + try { + const response = await fetch(`${API_URL}/user/list`); + const data: Account[] = await response.json(); + setAccounts(data); + } catch (error) { + console.error('Error fetching data', error); + } + }; + + fetchAccount(); + }, []); + + // const fetchAccount = async (page: number, limit: number, sorting: any, filter: any) => { + // try { + // const response = await GetData(`${API_URL}/user/list`, { + // limit: limit, + // page: page + 1, + // with_deleted: true, + // order_field: sorting[0].id, + // order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', + // filter: JSON.stringify(filter) + // }); + + // setAccounts(response?.data.list); + + // return { + // data: response?.data.list, + // totalCount: response?.data.total_count + // }; + // } catch (error) { + // console.log(error); + // } + // }; + + return ( +
+
+

Manage Account

+ +
+
+ ); +}; + +export default ManageAccount; diff --git a/src/pages/account/manage-account/hooks/ManageAccountContext.tsx b/src/pages/account/manage-account/hooks/ManageAccountContext.tsx new file mode 100644 index 0000000..80ac1c0 --- /dev/null +++ b/src/pages/account/manage-account/hooks/ManageAccountContext.tsx @@ -0,0 +1,3 @@ +import { apiConfig } from '@/config/api.config'; + +const API_URL = apiConfig.service_dashboard; diff --git a/src/pages/account/manage-currency/ManageCurrency.tsx b/src/pages/account/manage-currency/ManageCurrency.tsx new file mode 100644 index 0000000..a9b89e9 --- /dev/null +++ b/src/pages/account/manage-currency/ManageCurrency.tsx @@ -0,0 +1,18 @@ +import { Container, DataGridInner } from '@/components'; +import { ManageCurrencyContextProvider } from './hooks/ManageCurrencyContext'; +import AddDialog from './blocks/AddDialog'; + +const ManageCurrency = () => { + return ( + + +
+ +
+ +
+
+ ); +}; + +export default ManageCurrency; diff --git a/src/pages/account/manage-currency/blocks/AddDialog.tsx b/src/pages/account/manage-currency/blocks/AddDialog.tsx new file mode 100644 index 0000000..3394fe0 --- /dev/null +++ b/src/pages/account/manage-currency/blocks/AddDialog.tsx @@ -0,0 +1,240 @@ +import { apiConfig } from '@/config/api.config'; +import { useRef, useState } from 'react'; +import { useManageCurrencyContext } from '../hooks/useManageAccessTypeContext'; +import { Alert, KeenIcon, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_dashboard; + +const AddDialog = () => { + const parentRef = useRef(null); + const { + showAddDialog, + handleAddDialog, + currencies, + showEditDialog, + handleEditDialog, + selectedUser + } = useManageCurrencyContext(); + + const { reload } = useDataGrid(); + const { PostData, PutData } = useCallApi(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const initialState = { + name: '', + code: '', + prefix: '', + trailer: '', + format: '', + grouping_separator: '', + decimal_separator: '' + }; + + const [formField, setFormField] = useState(initialState); + const resetForm = () => { + setFormField(initialState); + }; + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if ( + formField.name === '' || + formField.code === '' || + formField.prefix === '' || + formField.format === '' || + formField.decimal_separator === '' || + formField.grouping_separator === '' + ) { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + const handleReset = () => { + setFormField(initialState); + }; + + return ( + handleAddDialog(open)}> + + + + +
+
+

Create Currency

+
+
+
{ + handleAddDialog(false); + resetForm(); + }} + > + +
+
+
+ +
+ {alert.show && ( + + {alert.message} + + )} +
+
+
+
+ + + setFormField((prev) => ({ ...prev, name: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, code: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, prefix: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, trailer: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, format: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, grouping_separator: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, decimal_separator: target.value })) + } + /> +
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/account/manage-currency/blocks/ListToolBar.tsx b/src/pages/account/manage-currency/blocks/ListToolBar.tsx new file mode 100644 index 0000000..53af0d6 --- /dev/null +++ b/src/pages/account/manage-currency/blocks/ListToolBar.tsx @@ -0,0 +1,49 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useManageCurrencyContext } from '../hooks/useManageAccessTypeContext'; +import { Button } from '@/components/ui/button'; + +const ListToolbar = () => { + const { table, reload } = useDataGrid(); + const { handleAddDialog, handleEditDialog } = useManageCurrencyContext(); + + return ( +
+
+
+
+ + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/account/manage-currency/hooks/ManageCurrencyContext.tsx b/src/pages/account/manage-currency/hooks/ManageCurrencyContext.tsx new file mode 100644 index 0000000..c5cd102 --- /dev/null +++ b/src/pages/account/manage-currency/hooks/ManageCurrencyContext.tsx @@ -0,0 +1,259 @@ +import { DataGridColumnHeader, DataGridProvider } from '@/components'; +import { Toaster } from '@/components/ui/sonner'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { ColumnDef } from '@tanstack/react-table'; +import { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import ListToolbar from '../blocks/ListToolBar'; + +interface SelectedUser { + id: string; + name: string; + code: string; + prefix: string; + trailer: string; + format: string; + grouping_separator: string; + decimal_separator: string; +} + +interface CurrencyProps { + id: string; + name: string; + code: string; + prefix: string; + trailer: string; + format: string; + grouping_separator: string; + decimal_separator: string; +} + +interface ContextProps { + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_user: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + selectedUser: string | null; + currencies: CurrencyProps[]; +} + +const initialProps: ContextProps = { + showEditDialog: false, + handleEditDialog: () => {}, + showAddDialog: false, + handleAddDialog: () => {}, + selectedUser: null, + currencies: [] +}; + +const ManageCurrencyContext = createContext(initialProps); +const API_URL = apiConfig.service_dashboard; + +const ManageCurrencyContextProvider = ({ children }: { children: React.ReactNode }) => { + const [showEditDialog, setShowEditDialog] = useState(false); + const [showAddDialog, setShowAddDialog] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [currencies, setCurrencies] = useState([]); + const { GetData } = useCallApi(); + + useEffect(() => { + const dummyData: CurrencyProps[] = [ + { + id: '1', + name: 'US Dollar', + code: 'USD', + prefix: '$', + trailer: '', + format: '#,##0.00', + grouping_separator: ',', + decimal_separator: '.' + }, + { + id: '2', + name: 'Euro', + code: 'EUR', + prefix: '€', + trailer: '', + format: '#.##0,00', + grouping_separator: '.', + decimal_separator: ',' + }, + { + id: '3', + name: 'Japanese Yen', + code: 'JPY', + prefix: '¥', + trailer: '', + format: '#,##0', + grouping_separator: ',', + decimal_separator: '.' + }, + { + id: '4', + name: 'Indonesia Rupiah', + code: 'IDR', + prefix: 'Rp', + trailer: '', + format: '#,##0', + grouping_separator: '.', + decimal_separator: ',' + } + ]; + setCurrencies(dummyData); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_user: string | null) => { + setSelectedUser(show ? selected_user : null); + setShowEditDialog(show); + }, []); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.code, + id: 'code', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.prefix, + id: 'prefix', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.trailer, + id: 'trailer', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.format, + id: 'format', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.grouping_separator, + id: 'grouping_separator', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.decimal_separator, + id: 'decimal_separator', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + id: 'actions', + enableSorting: false, + header: ({ column }) => , + cell: ({ row }) => { + return ( + <> + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleAddDialog, handleEditDialog] + ); + console.log(currencies); + return ( +
+
+

Manage Currency

+
+ + + + {currencies.length > 0 ? ( + } + sorting={[{ id: 'id', desc: true }]} + serverSide={true} + > + {children} + + ) : ( +

loading

+ )} +
+
+ ); +}; + +export { ManageCurrencyContext, ManageCurrencyContextProvider }; +export type { SelectedUser }; diff --git a/src/pages/account/manage-currency/hooks/useManageAccessTypeContext.tsx b/src/pages/account/manage-currency/hooks/useManageAccessTypeContext.tsx new file mode 100644 index 0000000..d1f682c --- /dev/null +++ b/src/pages/account/manage-currency/hooks/useManageAccessTypeContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageCurrencyContext } from './ManageCurrencyContext'; + +const useManageCurrencyContext = () => { + const context = useContext(ManageCurrencyContext); + + if (!context) throw new Error('useManageCurrencyContext must be used within AuthProvider'); + + return context; +}; + +export { useManageCurrencyContext }; diff --git a/src/pages/dashboards/home/DashboardHomePage.tsx b/src/pages/dashboards/home/DashboardHomePage.tsx index fa75b05..61e3af5 100644 --- a/src/pages/dashboards/home/DashboardHomePage.tsx +++ b/src/pages/dashboards/home/DashboardHomePage.tsx @@ -14,6 +14,8 @@ import { useFetchCardData, useFetchChartData } from './hooks'; import { Button } from '@/components/ui/button'; import { useFetchYear } from './hooks/useFetchYear'; import { get5LastYear } from '@/utils/Date'; +import { staticChartData } from './staticChart'; + // sum -> nominal, count-> total type CountType = 'sum' | 'count'; type ChartType = 'line' | 'bar'; @@ -184,9 +186,9 @@ const DashboardHomePage = () => {
data.data)} chartType={chartType} chartLegend={chartLegend} /> diff --git a/src/pages/dashboards/home/blocks/Chart.tsx b/src/pages/dashboards/home/blocks/Chart.tsx index 34f2df5..25cdad0 100644 --- a/src/pages/dashboards/home/blocks/Chart.tsx +++ b/src/pages/dashboards/home/blocks/Chart.tsx @@ -37,7 +37,7 @@ const Chart = ({ plotOptions: { bar: { horizontal: false, - columnWidth: '50%' + columnWidth: '30%' } }, dataLabels: { diff --git a/src/pages/dashboards/home/staticChart.tsx b/src/pages/dashboards/home/staticChart.tsx new file mode 100644 index 0000000..1c777f1 --- /dev/null +++ b/src/pages/dashboards/home/staticChart.tsx @@ -0,0 +1,56 @@ +export const staticChartData = { + series: [ + { + categories: 'Jan', + data: [ + { + x: 'Jan 01', + y: 50 + } + ] + }, + { + data: [ + { + x: 'Jan 02', + y: 75 + } + ] + }, + { + data: [ + { + x: 'Jan 03', + y: 200 + } + ] + }, + { + data: [ + { + x: 'Jan 04', + y: 125 + } + ] + }, + { + data: [ + { + x: 'Jan 05', + y: 150 + } + ] + }, + { + data: [ + { + x: 'Jan 06', + y: 175 + } + ] + } + ], + xaxis: { + categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + } +}; diff --git a/src/pages/groups/Column.tsx b/src/pages/groups/Column.tsx new file mode 100644 index 0000000..9809155 --- /dev/null +++ b/src/pages/groups/Column.tsx @@ -0,0 +1,37 @@ +import { ColumnDef } from '@tanstack/react-table'; + +export type Group = { + id: number; + createdDate: Date; + name: string; + status: string; + description: string; +}; + +export const columns: ColumnDef[] = [ + { + accessorKey: 'id', + header: 'ID' + }, + { + accessorKey: 'createdDate', + header: 'Created Date', + cell: ({ row }) => new Date(row.original.createdDate).toLocaleDateString() + }, + { + accessorKey: 'name', + header: 'Name' + }, + { + accessorKey: 'status', + header: 'Status' + }, + { + accessorKey: 'description', + header: 'Description' + }, + // { + // id: 'actions', + // header: 'Actions' + // } +]; diff --git a/src/pages/groups/ManageGroups.tsx b/src/pages/groups/ManageGroups.tsx new file mode 100644 index 0000000..2ede523 --- /dev/null +++ b/src/pages/groups/ManageGroups.tsx @@ -0,0 +1,249 @@ +import { DataTable } from '@/components/ui/DataTable'; +import { columns, Group } from './Column'; +import { apiConfig } from '@/config/api.config'; +import axios, { AxiosResponse } from 'axios'; +import { DialogContent, MenuItem, Radio, RadioGroup, FormControlLabel, FormControl, + Dialog, DialogActions, DialogTitle, Typography, Button, Box } from '@mui/material'; +import { useState, useEffect } from 'react'; +// import IconButton from '@mui/material/IconButton'; +import CloseIcon from '@mui/icons-material/Close'; +import Divider from '@mui/material/Divider'; +import ConfirmDialog from '@/components/confirm'; +// import { DialogHeader } from '@/components/ui/dialog'; +// import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +const BASE_URL = apiConfig.service_customer; + +let initGroup = { + id: '', + groupName: '', + status: '', + description: '', + pin_length: '', + max_pin_attempts: '', + default_notification: '' +} + +const ManageGroups = () => { + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [dataGroup, setDataGroup] = useState([]); + const [formData, setFormData] = useState(initGroup); + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [dialogType, setDialogType] = useState(''); + const [dialogOpen, setDialogOpen] = useState(false); + + useEffect(() => { + fetchGroups() + }, []); + + async function fetchGroups() { + try { + let groups = await axios.get(`${BASE_URL}/groups/list`, { + params: { + limit: 10, + page: 1, + with_deleted: false, + order_field: 'name', + order_direction: 'ASC' + } + }); + setDataGroup(groups.data.data.list) + } catch (error: any) { + alert(error.message) + console.log(error); + } + } + + const openDialog = () => setIsDialogOpen(true); + const closeDialog = () => { + setIsDialogOpen(false) + setFormData(initGroup) + }; + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }) + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setDialogOpen(true); + }; + + function createGroup() { + setDialogType('create'); + openDialog(); + } + + const handleUpdate = (group: any) => { + setFormData({ + ...formData, + id: group.id, + groupName: group.name, + status: group.status, + description: group.description + }) + setDialogType('update'); + setIsDialogOpen(true) + }; + + const handleDelete = (group: any) => { + setFormData({ + ...formData, + id: group.id, + groupName: group.name, + status: group.status, + description: group.description + }) + setDialogType('delete'); + setDialogOpen(true) + }; + + const handleYes = async () => { + try { + if (dialogType === 'create') { + await axios.post(`${BASE_URL}/groups/create`, { + "name": formData.groupName, + "status": formData.status, + "created_at": new Date() + }) + } else if (dialogType === 'update') { + await axios.put(`${BASE_URL}/groups/update/${formData.id}`, { + "name": formData.groupName, + "status": formData.status, + "updated_at": new Date() + }) + } else if (dialogType === 'delete') { + await axios.delete(`${BASE_URL}/groups/delete/${formData.id}/true`) + } + await fetchGroups(); + closeDialog(); + setDialogOpen(false); + } catch (error) { + console.log(error); + closeDialog(); + setDialogOpen(false); + } + }; + + const handleNo = () => { + setDialogOpen(false); + }; + + return ( +
+
+ setDialogOpen(false)} + title="Confirm Action" + content={`Are you sure you want to `+( dialogType === 'create' ? "create?" : ( dialogType === 'update' ? "update?" : "delete?"))} + onYes={handleYes} + onNo={handleNo} + /> +

Groups

+ + + +
+ Create New Group + + + +
+ +
+
+
+ + +
+
+ + + + } label="Yes" /> + } label="No" /> + + +
+
+ + +
+ {/*
+ + +
+
+ + +
*/} + {/*
+ + + +
*/} + +
+
+
+
+
+
+ ); +}; + +export default ManageGroups; diff --git a/src/pages/master/MasterData.tsx b/src/pages/master/MasterData.tsx new file mode 100644 index 0000000..4cdb4cb --- /dev/null +++ b/src/pages/master/MasterData.tsx @@ -0,0 +1,11 @@ +const MasterData = () => { + return ( +
+
+

Master Data Page

+
+
+ ); +}; + +export default MasterData; diff --git a/src/pages/master/aldeias/AldeiasMaster.tsx b/src/pages/master/aldeias/AldeiasMaster.tsx new file mode 100644 index 0000000..851f5f1 --- /dev/null +++ b/src/pages/master/aldeias/AldeiasMaster.tsx @@ -0,0 +1,11 @@ +const AldeiasMaster = () => { + return ( +
+
+

Aldeias Master Data

+
+
+ ); +}; + +export default AldeiasMaster; diff --git a/src/pages/master/municipios/Municipios.tsx b/src/pages/master/municipios/Municipios.tsx new file mode 100644 index 0000000..0538fbd --- /dev/null +++ b/src/pages/master/municipios/Municipios.tsx @@ -0,0 +1,21 @@ +import { Container, DataGridInner } from '@/components'; +import { ManageMunicipiosProvider } from './hooks/ManageMunicipiosContext'; +import AddDialog from './blocks/AddDialog'; +import SearchDialog from './blocks/SearchDialog'; + +const Municipios = () => { + return ( + + +

MUNICIPIOS

+
+ +
+ + +
+
+ ); +}; + +export default Municipios; diff --git a/src/pages/master/municipios/blocks/AddDialog.tsx b/src/pages/master/municipios/blocks/AddDialog.tsx new file mode 100644 index 0000000..2916f94 --- /dev/null +++ b/src/pages/master/municipios/blocks/AddDialog.tsx @@ -0,0 +1,111 @@ +import React, { useRef, useState } from 'react'; +import { useManageMunicipiosContext } from '../hooks/useManageMunicipiosContext'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Alert, KeenIcon } from '@/components'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +const AddDialog = () => { + const parentRef = useRef(null); + const { showAddDialog, handleAddDialog } = useManageMunicipiosContext(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + name: '' + }; + + const [formField, setFormField] = useState(initialState); + const resetForm = () => { + setFormField(initialState); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (formField.name === '') { + setAlert({ show: true, message: 'Please fill name field.' }); + return; + } + + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + const handleReset = () => { + setFormField(initialState); + }; + + return ( + handleAddDialog(open)}> + + + + +
+
+

Add Municipios

+
+
+
{ + handleAddDialog(false); + resetForm(); + }} + > + +
+
+
+ +
+ {alert.show && ( + + {alert.message} + + )} +
+
+
+ + + + setFormField((prev) => ({ ...prev, name: target.value })) + } + /> +
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/master/municipios/blocks/ListToolbar.tsx b/src/pages/master/municipios/blocks/ListToolbar.tsx new file mode 100644 index 0000000..0129c85 --- /dev/null +++ b/src/pages/master/municipios/blocks/ListToolbar.tsx @@ -0,0 +1,62 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useManageMunicipiosContext } from '../hooks/useManageMunicipiosContext'; +import { Button } from '@/components/ui/button'; + +const ListToolbar = () => { + const { table, reload } = useDataGrid(); + const { handleAddDialog, handleSearchDialog } = useManageMunicipiosContext(); + + return ( +
+
+
+
+ + + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/master/municipios/blocks/SearchDialog.tsx b/src/pages/master/municipios/blocks/SearchDialog.tsx new file mode 100644 index 0000000..4ba1a62 --- /dev/null +++ b/src/pages/master/municipios/blocks/SearchDialog.tsx @@ -0,0 +1,177 @@ +import { useRef, useState } from 'react'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Alert, KeenIcon } from '@/components'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { useManageMunicipiosContext } from '../hooks/useManageMunicipiosContext'; +import { apiConfig } from '@/config/api.config'; +import axios from 'axios'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; + +interface PostoAdms { + id: number; + name: string; +} + +const API_URL = apiConfig.service_master_data; + +const SearchDialog = () => { + const parentRef = useRef(null); + const { showSearchDialog, handleSearchDialog, municipios } = useManageMunicipiosContext(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + id: 0, + name: '' + }; + + const [formField, setFormField] = useState(initialState); + const resetForm = () => { + setFormField(initialState); + }; + const [postoadms, setPostoadms] = useState([]); + const [isFound, setIsFound] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const id = Number(formField.id); + + if (formField.id === 0) { + setAlert({ show: true, message: 'Please fill name field.' }); + return; + } + + try { + const response = await axios.get(`${API_URL}/municipios/postoadms/${id}`); + + if (response.data.status) { + setPostoadms(response.data.data); + setIsFound(true); + console.log('Found postoadms: ', response.data.data); + } else { + setPostoadms([]); + setIsFound(false); + setAlert({ show: true, message: 'No postoadms found.' }); + } + } catch (error) { + console.error('Error fetching postoadms', error); + setAlert({ show: true, message: 'Failed to fetch postoadms. Please try again.' }); + } + setAlert({ show: false, message: '' }); + }; + + const handleReset = () => { + setFormField(initialState); + setIsFound(false); + setPostoadms([]); + }; + // console.log(municipios); + return ( + handleSearchDialog(open)}> + + + + +
+
+

+ Search Postu Administravo +

+
+
+
{ + handleSearchDialog(false); + resetForm(); + }} + > + +
+
+
+ +
+ {alert.show && ( + + {alert.message} + + )} +
+
+
+ + + +
+ + {isFound && postoadms.length > 0 && ( +
+

Postu Administravo:

+
+
+ + {postoadms.map((posto) => posto.name).join(', ')} + +
+
+ )} + +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default SearchDialog; diff --git a/src/pages/master/municipios/hooks/ManageMunicipiosContext.tsx b/src/pages/master/municipios/hooks/ManageMunicipiosContext.tsx new file mode 100644 index 0000000..808f57e --- /dev/null +++ b/src/pages/master/municipios/hooks/ManageMunicipiosContext.tsx @@ -0,0 +1,250 @@ +import { DataGridColumnHeader, DataGridProvider } from '@/components'; +import { Toaster } from '@/components/ui/sonner'; +import { apiConfig } from '@/config/api.config'; +import { ColumnDef } from '@tanstack/react-table'; +import axios from 'axios'; +import React, { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallApi } from '@/hooks'; +import ListToolbar from '../blocks/ListToolbar'; +import { Button } from '@/components/ui/button'; +import { useNavigate } from 'react-router'; + +interface MunicipiosProps { + id: number; + name: string; +} + +interface ContextProps { + municipios: MunicipiosProps[]; + showSearchDialog: boolean; + handleSearchDialog: (show: boolean) => void; + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_user: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + showDeleteDialog: boolean; + handleDeleteDialog: (show: boolean, selected_user: string | null) => void; + selectedMunicipios: string | null; + getMunicipiosLists: ( + limit: number, + page: number, + with_deleted: boolean, + order_field: any, + order_direction: any + ) => Promise<{ data: MunicipiosProps[]; totalCount: number } | undefined>; +} + +const initialProps: ContextProps = { + municipios: [], + showSearchDialog: false, + handleSearchDialog: (show: boolean) => {}, + showEditDialog: false, + handleEditDialog: (show: boolean, selected_user: string | null) => {}, + showAddDialog: false, + handleAddDialog: (show: boolean) => {}, + showDeleteDialog: false, + handleDeleteDialog: (show: boolean, selected_user: string | null) => {}, + selectedMunicipios: null, + getMunicipiosLists: async () => ({ data: [], totalCount: 0 }) +}; + +// interface MunicipiosContext { +// municipios: MunicipiosProps[]; +// getMunicipiosLists: ( +// limit: number, +// page: number, +// with_deleted: boolean, +// order_field: any, +// order_direction: any +// ) => Promise; +// getMunicipiosByName: (name: string) => Promise; +// createMunicipios: (data: Partial) => Promise; +// updateMunicipios: (id: number, data: Partial) => Promise; +// deleteMunicipios: (id: number, hardDelete?: boolean) => Promise; +// restoreMunicipios: (id: number) => Promise; +// } + +const ManageMunicipiosContext = createContext(initialProps); +const API_URL = apiConfig.service_master_data; + +const ManageMunicipiosProvider = ({ children }: { children: React.ReactNode }) => { + const [showSearchDialog, setShowSearchDialog] = useState(false); + const [showEditDialog, setShowEditDialog] = useState(false); + const [showAddDialog, setShowAddDialog] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [selectedMunicipios, setSelectedMunicipios] = useState(null); + const [municipios, setMunicipios] = useState([]); + const { GetData } = useCallApi(); + const navigate = useNavigate(); + + const handleSearchDialog = useCallback((show: boolean) => { + setShowSearchDialog(show); + }, []); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_municipios: string | null) => { + setSelectedMunicipios(show ? selected_municipios : null); + setShowEditDialog(show); + }, []); + + const handleDeleteDialog = useCallback((show: boolean, selected_municipios: string | null) => { + setSelectedMunicipios(show ? selected_municipios : null); + setShowDeleteDialog(show); + }, []); + + const handleNavigate = (path: string) => { + const url = navigate(`${API_URL}/municipios/postoadms/${path}`); + console.log(url); + }; + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[1000px]' + } + }, + { + id: 'actions', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[100px], text-center', + cellClassName: 'text-center' + }, + cell: (info) => ( + + ) + } + ], + [handleEditDialog, handleDeleteDialog] + ); + + const getMunicipiosLists = async (page: number, limit: number, sorting: any, filter: any) => { + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; + const response = await axios.get(`${API_URL}/municipios/list`, { + params: { + limit: limit, + page: page + 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + } + }); + console.log(response.data); + setMunicipios(response.data.data.list); + return { data: response?.data.data.list, totalCount: response?.data.data.total_count }; + } catch (error) { + console.error('Error fetching municipios', error); + } + }; + + const getPostoadmsByMunicipio = async (name: string) => { + try { + const response = await axios.get(`${API_URL}/municipios/postoadms/${name}`); + const data = response.data; + console.log(data); + } catch (error) { + console.error(`Error fetching municipios by ${name}`, error); + } + }; + + const createMunicipios = async (data: Partial) => { + try { + await axios.post(`${API_URL}/municipios/create`, data); + // getMunicipiosLists(10, 1, false, 'name', 'ASC'); + } catch (error) { + console.error('Error creating municipios', error); + } + }; + + const updateMunicipios = async (id: number, data: Partial) => { + try { + await axios.put(`${API_URL}/update/${id}`, data); + // getMunicipiosLists(10, 1, false, 'name', 'ASC'); + } catch (error) { + console.error('Error updating municipios', error); + } + }; + + const deleteMunicipios = async (id: number, hardDelete?: boolean) => { + try { + await axios.delete(`${API_URL}/delete/${id}/${hardDelete}`); + // getMunicipiosLists(10, 1, false, 'name', 'ASC'); + } catch (error) { + console.error('Error deleting municipios', error); + } + }; + + const restoreMunicipios = async (id: number) => { + try { + await axios.put(`${API_URL}/restore/${id}`); + // getMunicipiosLists(10, 1, false, 'name', 'ASC'); + } catch (error) { + console.error('Error restoring municipios', error); + } + }; + console.log(municipios); + return ( + + + + } + layout={{ card: true }} + sorting={[{ id: 'id', desc: false }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + getMunicipiosLists(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + + ); +}; + +export { ManageMunicipiosProvider, ManageMunicipiosContext }; +export type { MunicipiosProps }; diff --git a/src/pages/master/municipios/hooks/useManageMunicipiosContext.tsx b/src/pages/master/municipios/hooks/useManageMunicipiosContext.tsx new file mode 100644 index 0000000..6dba2e7 --- /dev/null +++ b/src/pages/master/municipios/hooks/useManageMunicipiosContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageMunicipiosContext } from './ManageMunicipiosContext'; + +const useManageMunicipiosContext = () => { + const context = useContext(ManageMunicipiosContext); + + if (!context) throw new Error('useManageMunicipiosContext must be used within AuthProvider'); + + return context; +}; + +export { useManageMunicipiosContext }; diff --git a/src/pages/master/postoadms/PostoAdmsMaster.tsx b/src/pages/master/postoadms/PostoAdmsMaster.tsx new file mode 100644 index 0000000..a8e00d4 --- /dev/null +++ b/src/pages/master/postoadms/PostoAdmsMaster.tsx @@ -0,0 +1,17 @@ +import { ManagePostoAdmsContextProvider } from './hooks/ManagePostoAdmsContext'; +import { Container, DataGridInner } from '@/components'; + +const PostoAdmsMaster = () => { + return ( + + +

Postu Administrativo

+
+ +
+
+
+ ); +}; + +export default PostoAdmsMaster; diff --git a/src/pages/master/postoadms/blocks/ListToolbar.tsx b/src/pages/master/postoadms/blocks/ListToolbar.tsx new file mode 100644 index 0000000..a05e53c --- /dev/null +++ b/src/pages/master/postoadms/blocks/ListToolbar.tsx @@ -0,0 +1,63 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; + +import { Button } from '@/components/ui/button'; +import { useManagePostoAdmsContext } from '../hooks/useManagePostoAdmsContext'; + +const ListToolbar = () => { + const { table, reload } = useDataGrid(); + const { handleAddDialog, handleSearchDialog } = useManagePostoAdmsContext(); + + return ( +
+
+
+
+ + + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/master/postoadms/hooks/ManagePostoAdmsContext.tsx b/src/pages/master/postoadms/hooks/ManagePostoAdmsContext.tsx new file mode 100644 index 0000000..2b8858f --- /dev/null +++ b/src/pages/master/postoadms/hooks/ManagePostoAdmsContext.tsx @@ -0,0 +1,178 @@ +import { DataGridColumnHeader, DataGridProvider } from '@/components'; +import { Button } from '@/components/ui/button'; +import { Toaster } from '@/components/ui/sonner'; +import { apiConfig } from '@/config/api.config'; +import { ColumnDef } from '@tanstack/react-table'; +import axios from 'axios'; +import { createContext, useCallback, useMemo, useState } from 'react'; +import { useNavigate, useParams } from 'react-router'; +import ListToolbar from '../blocks/ListToolbar'; + +interface PostoAdmsProps { + id: number; + name: string; +} + +interface ContextProps { + showSearchDialog: boolean; + handleSearchDialog: (show: boolean) => void; + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_postoAdms: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + showDeleteDialog: boolean; + handleDeleteDialog: (show: boolean, selected_postoAdms: string | null) => void; + selectedPostoAdms: string | null; + getPostoAdmsLists: ( + limit: number, + page: number, + with_deleted: boolean, + order_field: any, + order_direction: any + ) => Promise<{ data: PostoAdmsProps[]; totalCount: number } | undefined>; +} + +const initialProps: ContextProps = { + showSearchDialog: false, + handleSearchDialog: (show: boolean) => {}, + showEditDialog: false, + handleEditDialog: (show: boolean, selected_postoAdms: string | null) => {}, + showAddDialog: false, + handleAddDialog: (show: boolean) => {}, + showDeleteDialog: false, + handleDeleteDialog: (show: boolean, selected_postoAdms: string | null) => {}, + selectedPostoAdms: null, + getPostoAdmsLists: async () => ({ data: [], totalCount: 0 }) +}; + +const ManagePostoAdmsContext = createContext(initialProps); +const API_URL = apiConfig.service_master_data; + +const ManagePostoAdmsContextProvider = ({ children }: { children: React.ReactNode }) => { + const [showSearchDialog, setShowSearchDialog] = useState(false); + const [showAddDialog, setShowAddDialog] = useState(false); + const [showEditDialog, setShowEditDialog] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [selectedPostoAdms, setSelectedPostoAdms] = useState(null); + + const navigate = useNavigate(); + const { municipioId } = useParams(); + + const handleSearchDialog = useCallback((show: boolean) => { + setShowSearchDialog(show); + }, []); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_postoAdms: string | null) => { + setSelectedPostoAdms(show ? selected_postoAdms : null); + setShowEditDialog(show); + }, []); + + const handleDeleteDialog = useCallback((show: boolean, selected_postoAdms: string | null) => { + setSelectedPostoAdms(show ? selected_postoAdms : null); + setShowDeleteDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[1000px]' + } + }, + { + id: 'actions', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + meta: { + headerClassName: 'w-[100px], text-center', + cellClassName: 'text-center' + }, + cell: (info) => ( + + ) + } + ], + [handleEditDialog, handleDeleteDialog] + ); + + const getPostoAdmsLists = async (page: number, limit: number, sorting: any, filter: any) => { + sorting: sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; + try { + const response = await axios.get(`${API_URL}/municipios/postoadms/${municipioId}`, { + params: { + limit, + page: page + 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc ? 'DESC' : 'ASC' + } + }); + console.log(response.data); + return { data: response.data.data, totalCount: response.data.data.total_count }; + } catch (error) { + console.error('Error fetching Postu Administrativo', error); + } + }; + + return ( + + + + } + layout={{ card: true }} + sorting={[{ id: 'id', desc: false }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + getPostoAdmsLists(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + + ); +}; + +export { ManagePostoAdmsContextProvider, ManagePostoAdmsContext }; +export type { PostoAdmsProps }; diff --git a/src/pages/master/postoadms/hooks/useManagePostoAdmsContext.tsx b/src/pages/master/postoadms/hooks/useManagePostoAdmsContext.tsx new file mode 100644 index 0000000..8665426 --- /dev/null +++ b/src/pages/master/postoadms/hooks/useManagePostoAdmsContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManagePostoAdmsContext } from './ManagePostoAdmsContext'; + +const useManagePostoAdmsContext = () => { + const context = useContext(ManagePostoAdmsContext); + + if (!context) throw new Error('useManagePostoAdmsContext must be used within AuthProvider'); + + return context; +}; + +export { useManagePostoAdmsContext }; diff --git a/src/pages/master/sucos/SucosMaster.tsx b/src/pages/master/sucos/SucosMaster.tsx new file mode 100644 index 0000000..6feede3 --- /dev/null +++ b/src/pages/master/sucos/SucosMaster.tsx @@ -0,0 +1,11 @@ +const SucosMaster = () => { + return ( +
+
+

Sucos Master Data

+
+
+ ); +}; + +export default SucosMaster; diff --git a/src/pages/members/kyc/Kyc.tsx b/src/pages/members/kyc/Kyc.tsx new file mode 100644 index 0000000..50fc1a2 --- /dev/null +++ b/src/pages/members/kyc/Kyc.tsx @@ -0,0 +1,42 @@ +import { DataGridInner, TDataGridProps } from '@/components'; +import { Table } from '@tanstack/react-table'; +import React, { createContext, useContext, useState } from 'react'; +import { ManageKycContextProvider } from './hooks'; + +export interface IDataGridContextProps { + props: TDataGridProps; + table: Table; + totalRows: number; + loading: (state: boolean) => void; + reload: () => void; + children?: React.ReactNode; +} + +const DataGridContext = createContext | undefined>(undefined); + +export const useDataGrid = () => { + const context = useContext(DataGridContext); + if (!context) { + throw new Error('useDataGrid must be used within a DataGridProvider'); + } + return context; +}; + +const Kyc = () => { + const [loading, setLoading] = useState(false); + + return ( +
+
+

Manage Member KYC

+ +
+ +
+
+
+
+ ); +}; + +export default Kyc; diff --git a/src/pages/members/kyc/blocks/AddDialog.tsx b/src/pages/members/kyc/blocks/AddDialog.tsx new file mode 100644 index 0000000..68b4d58 --- /dev/null +++ b/src/pages/members/kyc/blocks/AddDialog.tsx @@ -0,0 +1,14 @@ +import { Dialog } from '@/components/ui/dialog'; +import { useRef } from 'react'; +import { useKycContext } from '../hooks'; +import { useDataGrid } from '@/components'; + +const AddDialog = () => { + const parentRef = useRef(null); + const { showAddDialog, handleAddDialog } = useKycContext(); + const { reload } = useDataGrid(); + + return ; +}; + +export default AddDialog; diff --git a/src/pages/members/kyc/blocks/ListToolBar.tsx b/src/pages/members/kyc/blocks/ListToolBar.tsx new file mode 100644 index 0000000..8d99350 --- /dev/null +++ b/src/pages/members/kyc/blocks/ListToolBar.tsx @@ -0,0 +1,48 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { Button } from '@/components/ui/button'; +import { useKycContext } from '../hooks'; + +const ListToolBar = () => { + const { table, reload } = useDataGrid(); + const { handleDetailDialog, handleAddDialog } = useKycContext(); + + return ( +
+
+
+
+ + + + +
+
+ + + +
+
+
+
+ ); +}; + +export { ListToolBar }; diff --git a/src/pages/members/kyc/hooks/ManageKycContext.tsx b/src/pages/members/kyc/hooks/ManageKycContext.tsx new file mode 100644 index 0000000..2a63fe1 --- /dev/null +++ b/src/pages/members/kyc/hooks/ManageKycContext.tsx @@ -0,0 +1,145 @@ +import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; +import { Toaster } from '@/components/ui/sonner'; +import { apiConfig } from '@/config/api.config'; +import { ColumnDef } from '@tanstack/react-table'; +import { createContext, useCallback, useMemo, useState } from 'react'; +import { ListToolBar } from '../blocks/ListToolBar'; + +interface SelectedUser { + id: string; + name: string; + email: string; + role: string; + description: string; + created_date: Date; +} + +interface ContextProps { + showDetailDialog: boolean; + handleDetailDialog: (show: boolean, selected_user: SelectedUser | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + selectedUser: SelectedUser | null; +} + +const initialProps: ContextProps = { + showDetailDialog: false, + handleDetailDialog: () => {}, + showAddDialog: false, + handleAddDialog: () => {}, + selectedUser: null +}; + +const ManageKycContext = createContext(initialProps); +const API_URL = apiConfig.service_dashboard; + +const ManageKycContextProvider = ({ children }: { children: React.ReactNode }) => { + const [showDetailDialog, setShowDetailDialog] = useState(false); + const [showAddDialog, setShowAddDialog] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + + const handleDetailDialog = useCallback((show: boolean, selected_user: SelectedUser | null) => { + setSelectedUser(show ? selected_user : null); + setShowDetailDialog(show); + }, []); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.created_date, + id: 'created_date', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[350px]' + } + }, + { + accessorFn: (row) => row.description, + id: 'description', + header: ({ column }) => , + enableSorting: true, + meta: { + headerClassName: 'w-[350px]' + } + }, + { + id: 'actions', + enableSorting: false, + header: ({ column }) => , + cell: (data: any) => { + const row = data.row.original; + + return ( + <> + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleDetailDialog] + ); + + return ( + + + + } + layout={{ card: true }} + sorting={[{ id: 'name', desc: false }]} + serverSide={true} + > + {children} + + + ); +}; + +export { ManageKycContextProvider, ManageKycContext }; +export type { SelectedUser }; diff --git a/src/pages/members/kyc/hooks/index.ts b/src/pages/members/kyc/hooks/index.ts new file mode 100644 index 0000000..246dfa8 --- /dev/null +++ b/src/pages/members/kyc/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './ManageKycContext'; +export * from './useKycContext'; diff --git a/src/pages/members/kyc/hooks/useKycContext.tsx b/src/pages/members/kyc/hooks/useKycContext.tsx new file mode 100644 index 0000000..1043f87 --- /dev/null +++ b/src/pages/members/kyc/hooks/useKycContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageKycContext } from './ManageKycContext'; + +const useKycContext = () => { + const context = useContext(ManageKycContext); + + if (!context) throw new Error('useKycContext must be used within AuthProvider'); + + return context; +}; + +export { useKycContext }; diff --git a/src/pages/members/manage-members/Columns.tsx b/src/pages/members/manage-members/Columns.tsx new file mode 100644 index 0000000..c217e7f --- /dev/null +++ b/src/pages/members/manage-members/Columns.tsx @@ -0,0 +1,119 @@ +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger +} from '@/components/ui/dropdown-menu'; +import { ColumnDef } from '@tanstack/react-table'; +import { ArrowUpDown, MoreHorizontal } from 'lucide-react'; + +export type Members = { + id: number; + username: number; + name: string; + group: string; + email: string; + createdDate: Date; +}; + +export const columns: ColumnDef[] = [ + { + accessorKey: 'id', + header: ({ column }) => { + return ( + + ); + } + }, + { + accessorKey: 'username', + header: ({ column }) => { + return ( + + ); + } + }, + { + accessorKey: 'name', + header: ({ column }) => { + return ( + + ); + } + }, + { + accessorKey: 'email', + header: ({ column }) => { + return ( + + ); + } + }, + { + accessorKey: 'createdDate', + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => new Date(row.original.createdDate).toLocaleDateString() + }, + { + id: 'actions', + cell: ({ row }) => { + const dataMembers = row.original; + + return ( + + + + + + Actions + navigator.clipboard.writeText(dataMembers.id.toString())} + > + Copy account ID + + {/* */} + + + ); + } + } +]; diff --git a/src/pages/members/manage-members/ManageMembers.tsx b/src/pages/members/manage-members/ManageMembers.tsx new file mode 100644 index 0000000..3a099a3 --- /dev/null +++ b/src/pages/members/manage-members/ManageMembers.tsx @@ -0,0 +1,42 @@ +import { DataTable } from '@/components/ui/DataTable'; +import { columns, Members } from './Columns'; + +const dataMembers: Members[] = [ + { + id: 37026, + username: 67076807158, + name: 'Jumentino Carlos Luis da Costa', + group: 'REGULER', + email: 'Jumentinocldacoata@gmail.com', + createdDate: new Date('2025-02-21') + }, + { + id: 37027, + username: 67071827345, + name: 'Diego Costa', + group: 'REGULER', + email: 'DiegoCosta@gmail.com', + createdDate: new Date('2025-02-21') + }, + { + id: 37028, + username: 56123764212, + name: 'Luis Da Vista', + group: 'SUPERVISOR', + email: 'LuisdaVista@gmail.com', + createdDate: new Date('2024-01-21') + } +]; + +const ManageMembers = () => { + return ( +
+
+

Manage Members

+ +
+
+ ); +}; + +export default ManageMembers; diff --git a/src/pages/menu/manage-menu/ManageMenu.tsx b/src/pages/menu/manage-menu/ManageMenu.tsx new file mode 100644 index 0000000..e9febec --- /dev/null +++ b/src/pages/menu/manage-menu/ManageMenu.tsx @@ -0,0 +1,11 @@ +const ManageMenu = () => { + return ( +
+
+

Manage Menu

+
+
+ ); +}; + +export default ManageMenu; diff --git a/src/pages/menu/menu-category/MenuCategory.tsx b/src/pages/menu/menu-category/MenuCategory.tsx new file mode 100644 index 0000000..a43e69f --- /dev/null +++ b/src/pages/menu/menu-category/MenuCategory.tsx @@ -0,0 +1,11 @@ +const MenuCategory = () => { + return ( +
+
+

Menu Category

+
+
+ ); +}; + +export default MenuCategory; diff --git a/src/pages/menu/welcome/Welcome.tsx b/src/pages/menu/welcome/Welcome.tsx new file mode 100644 index 0000000..6446120 --- /dev/null +++ b/src/pages/menu/welcome/Welcome.tsx @@ -0,0 +1,11 @@ +const Welcome = () => { + return ( +
+
+

Welcome

+
+
+ ); +}; + +export default Welcome; diff --git a/src/pages/message/Inbox.tsx b/src/pages/message/Inbox.tsx new file mode 100644 index 0000000..c3d9f4c --- /dev/null +++ b/src/pages/message/Inbox.tsx @@ -0,0 +1,11 @@ +const Inbox = () => { + return ( +
+
+

Inbox

+
+
+ ); +}; + +export default Inbox; diff --git a/src/pages/notification/ManageNotification.tsx b/src/pages/notification/ManageNotification.tsx new file mode 100644 index 0000000..3f26388 --- /dev/null +++ b/src/pages/notification/ManageNotification.tsx @@ -0,0 +1,18 @@ +import { Container, DataGridInner } from '@/components'; +import { ManageNotifContextProvider } from './hooks/ManageNotificationContext'; +import AddDialog from './blocks/AddDialog'; + +const ManageNotification = () => { + return ( + + +
+ +
+ +
+
+ ); +}; + +export default ManageNotification; diff --git a/src/pages/notification/blocks/AddDialog.tsx b/src/pages/notification/blocks/AddDialog.tsx new file mode 100644 index 0000000..9daebf8 --- /dev/null +++ b/src/pages/notification/blocks/AddDialog.tsx @@ -0,0 +1,147 @@ +import { apiConfig } from '@/config/api.config'; +import { useRef, useState } from 'react'; +import { Alert, KeenIcon, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { useManageNotificationContext } from '../hooks/useManageNotificationContext'; + +const API_URL = apiConfig.service_dashboard; + +const AddDialog = () => { + const parentRef = useRef(null); + const { + handleAddDialog, + handleEditDialog, + showAddDialog, + showEditDialog, + selectedNotification, + notifications + } = useManageNotificationContext(); + + const { reload } = useDataGrid(); + const { PostData, PutData } = useCallApi(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const initialState = { + name: '', + destination_module: '' + }; + + const [formField, setFormField] = useState(initialState); + const resetForm = () => { + setFormField(initialState); + }; + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (formField.name === '' || formField.destination_module === '') { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + const handleReset = () => { + setFormField(initialState); + }; + + return ( + handleAddDialog(open)}> + + + + +
+
+

+ Create Notification +

+
+
+
{ + handleAddDialog(false); + resetForm(); + }} + > + +
+
+
+ +
+ {alert.show && ( + + {alert.message} + + )} +
+
+
+
+ + + setFormField((prev) => ({ ...prev, name: target.value })) + } + /> +
+
+ +
+
+ + + setFormField((prev) => ({ ...prev, destination_module: target.value })) + } + /> +
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/notification/blocks/ListToolbar.tsx b/src/pages/notification/blocks/ListToolbar.tsx new file mode 100644 index 0000000..aa88983 --- /dev/null +++ b/src/pages/notification/blocks/ListToolbar.tsx @@ -0,0 +1,57 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useManageNotificationContext } from '../hooks/useManageNotificationContext'; +import { Button } from '@/components/ui/button'; + +const ListToolBar = () => { + const { table, reload } = useDataGrid(); + const { handleAddDialog } = useManageNotificationContext(); + + return ( +
+
+
+
+ + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export { ListToolBar }; diff --git a/src/pages/notification/hooks/ManageNotificationContext.tsx b/src/pages/notification/hooks/ManageNotificationContext.tsx new file mode 100644 index 0000000..238922f --- /dev/null +++ b/src/pages/notification/hooks/ManageNotificationContext.tsx @@ -0,0 +1,142 @@ +import { DataGridColumnHeader, DataGridProvider } from '@/components'; +import { Toaster } from '@/components/ui/sonner'; +import { apiConfig } from '@/config/api.config'; +import { ColumnDef } from '@tanstack/react-table'; +import React, { createContext, useCallback, useMemo, useState } from 'react'; +import { ListToolBar } from '../blocks/ListToolbar'; + +interface ContextProps { + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_user: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + selectedNotification: string | null; + notifications: NotificationProps[]; +} + +interface SelectedNotification { + id: string; + name: string; + destination_module: string; +} + +interface NotificationProps { + id: string; + name: string; + destination_module: string; +} + +const initialProps: ContextProps = { + showEditDialog: false, + showAddDialog: false, + handleEditDialog: () => {}, + handleAddDialog: () => {}, + selectedNotification: null, + notifications: [] +}; + +const ManageNotifContext = createContext(initialProps); +const API_URL = apiConfig.service_dashboard; + +const ManageNotifContextProvider = ({ children }: { children: React.ReactNode }) => { + const [showEditDialog, setShowEditDialog] = useState(false); + const [showAddDialog, setShowAddDialog] = useState(false); + const [selectedNotification, setSelectedNotification] = useState(null); + const [notifications, setNotifications] = useState([]); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_notification: string | null) => { + setSelectedNotification(show ? selected_notification : null); + setShowEditDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false + }, + { + accessorFn: (row) => row.destination_module, + id: 'destination_module', + header: ({ column }) => , + enableSorting: true, + enableHiding: false + }, + { + id: 'actions', + header: ({ column }) => , + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + }, + cell: (data: any) => { + const row = data.row.original; + + return ( +
+ +
+ ); + } + } + ], + [handleEditDialog, handleAddDialog] + ); + + return ( +
+
+

Manage Notifications

+
+ + + + } + layout={{ card: true }} + sorting={[{ id: 'username', desc: false }]} + serverSide={true} + > + {children} + + +
+ ); +}; + +export { ManageNotifContext, ManageNotifContextProvider }; +export type { SelectedNotification }; diff --git a/src/pages/notification/hooks/useManageNotificationContext.tsx b/src/pages/notification/hooks/useManageNotificationContext.tsx new file mode 100644 index 0000000..ceb5f0a --- /dev/null +++ b/src/pages/notification/hooks/useManageNotificationContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageNotifContext } from './ManageNotificationContext'; + +const useManageNotificationContext = () => { + const context = useContext(ManageNotifContext); + + if (!context) throw new Error('useManageNotificationContext must be used within AuthProvider'); + + return context; +}; + +export { useManageNotificationContext }; diff --git a/src/pages/settings/user/manage-user/blocks/AddDialog.tsx b/src/pages/settings/user/manage-user/blocks/AddDialog.tsx index 2b4ee68..a7647cc 100644 --- a/src/pages/settings/user/manage-user/blocks/AddDialog.tsx +++ b/src/pages/settings/user/manage-user/blocks/AddDialog.tsx @@ -336,6 +336,7 @@ const AddDialog = () => {
+
+ +
+ + + + + + + ); +}; + +export default AddDialog; diff --git a/src/pages/transfer/blocks/ListToolBar.tsx b/src/pages/transfer/blocks/ListToolBar.tsx new file mode 100644 index 0000000..a3af2c3 --- /dev/null +++ b/src/pages/transfer/blocks/ListToolBar.tsx @@ -0,0 +1,49 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { useManageTransferTypeContext } from '../hooks/useManageTransferTypeContext'; +import { Button } from '@/components/ui/button'; + +const ListToolbar = () => { + const { table, reload } = useDataGrid(); + const { handleAddDialog, handleEditDialog } = useManageTransferTypeContext(); + + return ( +
+
+
+
+ + + + +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/transfer/hooks/ManageTransferTypeContext.tsx b/src/pages/transfer/hooks/ManageTransferTypeContext.tsx new file mode 100644 index 0000000..5a3d5d3 --- /dev/null +++ b/src/pages/transfer/hooks/ManageTransferTypeContext.tsx @@ -0,0 +1,178 @@ +import { DataGridColumnHeader, DataGridProvider } from '@/components'; +import { Toaster } from '@/components/ui/sonner'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { ColumnDef } from '@tanstack/react-table'; +import { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import ListToolbar from '../blocks/ListToolBar'; + +interface SelectedUser { + id: string; + name: string; + internal_name: string; + description: string; +} + +interface AccountProps { + id: string; + name: string; +} + +const accounts: string[] = [ + 'eMoney Account', + 'Topup Account', + 'Merchant Account', + 'Deposit Account', + 'Cash out/in' +]; + +interface ContextProps { + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_user: string | null) => void; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + selectedUser: string | null; + accounts: AccountProps[]; +} + +const initialProps: ContextProps = { + showEditDialog: false, + handleEditDialog: () => {}, + showAddDialog: false, + handleAddDialog: () => {}, + selectedUser: null, + accounts: [] +}; + +const ManageTransferTypeContext = createContext(initialProps); +const API_URL = apiConfig.service_dashboard; + +const ManageTransferTypeContextProvider = ({ children }: { children: React.ReactNode }) => { + const [showEditDialog, setShowEditDialog] = useState(false); + const [showAddDialog, setShowAddDialog] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [accounts, setAccount] = useState([]); + const { GetData } = useCallApi(); + + useEffect(() => { + setAccount([ + { id: '1', name: 'eMoney Account' }, + { id: '2', name: 'Topup Account' }, + { id: '3', name: 'Merchant Account' }, + { id: '4', name: 'Deposit Account' }, + { id: '5', name: 'Cash in/out Account' } + ]); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_user: string | null) => { + setSelectedUser(show ? selected_user : null); + setShowEditDialog(show); + }, []); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.id, + id: 'id', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[100px]' + } + }, + { + accessorFn: (row) => row.name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.internal_name, + id: 'internal_name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.description, + id: 'description', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + id: 'actions', + enableSorting: false, + header: ({ column }) => , + cell: ({ row }) => { + return ( + <> + + + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } + } + ], + [handleAddDialog, handleEditDialog] + ); + + return ( +
+
+

Manage Access Type

+
+ + + } + sorting={[{ id: 'id', desc: true }]} + serverSide={true} + > + {children} + + +
+ ); +}; + +export { ManageTransferTypeContext, ManageTransferTypeContextProvider }; +export type { SelectedUser }; diff --git a/src/pages/transfer/hooks/useManageTransferTypeContext.tsx b/src/pages/transfer/hooks/useManageTransferTypeContext.tsx new file mode 100644 index 0000000..b726a7c --- /dev/null +++ b/src/pages/transfer/hooks/useManageTransferTypeContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageTransferTypeContext } from './ManageTransferTypeContext'; + +const useManageTransferTypeContext = () => { + const context = useContext(ManageTransferTypeContext); + + if (!context) throw new Error('useManageAccessTypeContext must be used within AuthProvider'); + + return context; +}; + +export { useManageTransferTypeContext }; diff --git a/src/pages/webservice/ManageWebServices.tsx b/src/pages/webservice/ManageWebServices.tsx new file mode 100644 index 0000000..c85ecca --- /dev/null +++ b/src/pages/webservice/ManageWebServices.tsx @@ -0,0 +1,11 @@ +const ManageWebServices = () => { + return ( +
+
+

Manage Web Services

+
+
+ ); +}; + +export default ManageWebServices; diff --git a/src/routing/AppRoutingSetup.tsx b/src/routing/AppRoutingSetup.tsx index 33492a5..971fbaa 100644 --- a/src/routing/AppRoutingSetup.tsx +++ b/src/routing/AppRoutingSetup.tsx @@ -11,6 +11,25 @@ import ManageUserPage from '@/pages/settings/user/manage-user/ManageUserPage'; import LogActivityPage from '@/pages/settings/user/log-activity/LogActivityPage'; import ManagePositionPage from '@/pages/settings/user/manage-position/ManagePositionPage'; +import ManageAccount from '@/pages/account/manage-account/ManageAccount'; +import ManageGroups from '@/pages/groups/ManageGroups'; +import ManageMembers from '@/pages/members/manage-members/ManageMembers'; +import Kyc from '@/pages/members/kyc/Kyc'; +import AccessType from '@/pages/access/access-type/AccessType'; +import MemberCredential from '@/pages/access/member-credentials/MemberCredentials'; +import ManageCurrency from '@/pages/account/manage-currency/ManageCurrency'; +import ManageNotification from '@/pages/notification/ManageNotification'; +import MenuCategory from '@/pages/menu/menu-category/MenuCategory'; +import ManageMenu from '@/pages/menu/manage-menu/ManageMenu'; +import Welcome from '@/pages/menu/welcome/Welcome'; +import Inbox from '@/pages/message/Inbox'; +import ManageWebServices from '@/pages/webservice/ManageWebServices'; +import TransferType from '@/pages/transfer/TransferType'; +import MasterData from '@/pages/master/MasterData'; +import PostoAdmsMaster from '@/pages/master/postoadms/PostoAdmsMaster'; +import SucosMaster from '@/pages/master/sucos/SucosMaster'; +import AldeiasMaster from '@/pages/master/aldeias/AldeiasMaster'; +import Municipios from '@/pages/master/municipios/Municipios'; const AppRoutingSetup = (): ReactElement => { return ( @@ -18,8 +37,40 @@ const AppRoutingSetup = (): ReactElement => { }> }> } /> + + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + + } /> + } /> + + } /> + } /> + + } /> + } /> + + } /> + + } /> + + } /> + } /> + } /> + + } /> + + } /> + } /> } />