fix group menu

This commit is contained in:
unknown
2025-03-03 00:21:51 +07:00
71 changed files with 4252 additions and 15 deletions

View File

@ -2,6 +2,6 @@ VITE_APP_NAME=tpay-dashboard-tl
VITE_APP_VERSION=1=9.1.1 VITE_APP_VERSION=1=9.1.1
GENERATE_SOURCEMAP=false 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_APP_API_URL=http://127.0.0.1:4003/api
VITE_ENV=development VITE_ENV=development

26
package-lock.json generated
View File

@ -19,6 +19,7 @@
"@formatjs/intl-pluralrules": "^5.3.4", "@formatjs/intl-pluralrules": "^5.3.4",
"@formatjs/intl-relativetimeformat": "^11.4.4", "@formatjs/intl-relativetimeformat": "^11.4.4",
"@mui/base": "5.0.0-beta.40", "@mui/base": "5.0.0-beta.40",
"@mui/icons-material": "^6.4.6",
"@mui/material": "^6.1.6", "@mui/material": "^6.1.6",
"@mui/utils": "^6.1.6", "@mui/utils": "^6.1.6",
"@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-avatar": "^1.1.1",
@ -2646,6 +2647,31 @@
"url": "https://opencollective.com/mui-org" "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": { "node_modules/@mui/material": {
"version": "6.1.6", "version": "6.1.6",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz",

View File

@ -26,6 +26,7 @@
"@formatjs/intl-pluralrules": "^5.3.4", "@formatjs/intl-pluralrules": "^5.3.4",
"@formatjs/intl-relativetimeformat": "^11.4.4", "@formatjs/intl-relativetimeformat": "^11.4.4",
"@mui/base": "5.0.0-beta.40", "@mui/base": "5.0.0-beta.40",
"@mui/icons-material": "^6.4.6",
"@mui/material": "^6.1.6", "@mui/material": "^6.1.6",
"@mui/utils": "^6.1.6", "@mui/utils": "^6.1.6",
"@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-avatar": "^1.1.1",

View File

@ -41,12 +41,8 @@ const Login = () => {
setLoading(true); setLoading(true);
try { try {
if (!login) { if (!login) throw new Error('JWTProvider is required for this form.');
throw new Error('JWTProvider is required for this form.');
}
await login(values.username, values.password); await login(values.username, values.password);
// console.log(login);
if (values.remember) { if (values.remember) {
localStorage.setItem('username', values.username); localStorage.setItem('username', values.username);

View File

@ -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 (
<Dialog open={open} onClose={onClose}>
{title && <DialogTitle>{title}</DialogTitle>}
{content && <DialogContent>{content}</DialogContent>}
<DialogActions>
<Button onClick={onNo} color="secondary">
No
</Button>
<Button onClick={onYes} color="primary">
Yes
</Button>
</DialogActions>
</Dialog>
);
};
export default ConfirmDialog;

View File

@ -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<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
onUpdate: any;
onDelete: any;
}
export function DataTable<TData, TValue>({
columns,
data,
createGroup,
onUpdate,
onDelete
}: DataTableProps<TData, TValue> & { createGroup?: () => void }) {
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [sorting, setSorting] = useState<SortingState>([]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
onColumnFiltersChange: setColumnFilters,
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnFilters
}
});
return (
<div>
<div className="flex items-center py-4 justify-between">
<Input
placeholder="Search"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
className="max-w-sm"
/>
{createGroup && (
<Button variant="outline" onClick={createGroup}>
Create Group
</Button>
)}
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
<TableHead>Actions</TableHead>
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
<TableCell>
<div className="space-x-2">
<Button
className="bg-blue-500 text-white px-2 py-1 rounded"
onClick={() => onUpdate(row.original)}>
Update
</Button>
<Button
className="bg-red-500 text-white px-2 py-1 rounded"
onClick={() => onDelete(row.original)}>
Delete
</Button>
</div>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
);
}

View File

@ -1,12 +1,16 @@
interface apiConfigProps { interface apiConfigProps {
service_dashboard: string; service_dashboard: string;
service_customer: string;
service_master_data: string; service_master_data: string;
} }
const API_URL = import.meta.env.VITE_APP_API_URL; const API_URL = import.meta.env.VITE_APP_API_URL;
const apiConfig: apiConfigProps = { const apiConfig: apiConfigProps = {
service_dashboard: `${API_URL}${import.meta.env.VITE_ENV != 'development' ? '/d' : ""}`, // service_dashboard: `${API_URL}${import.meta.env.VITE_ENV != 'development' ? '/d' : ''}`,
service_master_data: `${API_URL}/m`, service_dashboard: `${API_URL}/d`,
service_customer: `${API_URL}/c`,
// service_master_data: `${API_URL}/m`
service_master_data: `${API_URL}/t`
}; };
export { apiConfig }; export { apiConfig };

View File

@ -19,7 +19,7 @@ const Header = () => {
return ( return (
<header <header
className={clsx( className={clsx(
'flex items-center transition-[height] shrink-0 h-[--tw-header-height] bg-[length:600px] bg-no-repeat', 'flex items-center transition-[height] shrink-0 h-[--tw-header-height] bg-[length:600px] bg-no-repeat bg-red-600',
headerSticky && headerSticky &&
'transition-[height] fixed z-10 top-0 left-0 right-0 shadow-sm backdrop-blur-md bg-white/70 dark:bg-coal-500/70 dark:border-b dark:border-b-coal-100' 'transition-[height] fixed z-10 top-0 left-0 right-0 shadow-sm backdrop-blur-md bg-white/70 dark:bg-coal-500/70 dark:border-b dark:border-b-coal-100'
)} )}

View File

@ -44,7 +44,7 @@ const HeaderLogo = () => {
/> */} /> */}
<img <img
src={toAbsoluteUrl('/media/app/app-logo.png')} src={toAbsoluteUrl('/media/app/app-logo.png')}
className="dark:hidden h-10" className="dark:hidden h-14"
alt="logo" alt="logo"
/> />
<img <img
@ -55,7 +55,7 @@ const HeaderLogo = () => {
</Link> </Link>
<div className="flex items-center"> <div className="flex items-center">
<h3 className="text-gray-700 text-base hidden md:block">TPAY Dashboard Portal</h3> <h3 className="text-gray-50 text-xl hidden md:block">TPAY Dashboard Portal</h3>
</div> </div>
</div> </div>
); );

View File

@ -3,7 +3,7 @@ import { NavbarMenu } from '../';
const Navbar = () => { const Navbar = () => {
return ( return (
<div className="border-b border-gray-200 pb-5 lg:pb-0 mb-5 lg:mb-5"> <div className="border-b border-gray-200 lg:pb-0 py-2 mb-5 mt-3 lg:mt-5">
<Container className="flex flex-wrap justify-between items-center gap-2"> <Container className="flex flex-wrap justify-between items-center gap-2">
<NavbarMenu /> <NavbarMenu />
</Container> </Container>

View File

@ -0,0 +1,21 @@
import { Container, DataGridInner } from '@/components';
import {
ManageAccessTypeContext,
ManageAccessTypeContextProvider
} from './hooks/ManageAccessTypeContext';
import AddDialog from './blocks/AddDialog';
const AccessType = () => {
return (
<ManageAccessTypeContextProvider>
<Container>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
<AddDialog />
</Container>
</ManageAccessTypeContextProvider>
);
};
export default AccessType;

View File

@ -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<any | null>(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<HTMLFormElement>) => {
e.preventDefault();
// setIsSubmitting(true);
console.log(formField);
};
const handleReset = () => {
setFormField(initialState);
};
return (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
<DialogHeader className="p-5 border-0">
<div className="flex items-center justify-between flex-wrap grow">
<div className="flex flex-col justify-center">
<h1 className="text-xl font-semibold leading-none text-gray-900">
Create Access Type
</h1>
<div className="flex items-center gap-2 text-sm font-normal text-gray-700"></div>
</div>
<div
className="cursor-pointer hover:opacity-100 opacity-50"
onClick={() => {
handleAddDialog(false);
resetForm();
}}
>
<KeenIcon icon="cross" className="text-1.5xl" />
</div>
</div>
</DialogHeader>
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
<div className="flex flex-col px-0">
{alert.show && (
<Alert variant="danger" className="mb-3">
<h3>{alert.message}</h3>
</Alert>
)}
<form action="" onSubmit={handleSubmit}>
<div className="card-body grid gap-5 p-0">
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">Name</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.name}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, name: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Description
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.description}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, description: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Internal Name
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.internal_name}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, internal_name: target.value }))
}
/>
</div>
</div>
<div className="flex justify-end pt-2.5 gap-5">
<Button variant={'outline'} type="reset" onClick={handleReset}>
Reset
</Button>
<Button variant={'default'} type="submit">
Save Changes
</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default AddDialog;

View File

@ -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 (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search Access Type"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/>
</label>
<DefaultTooltip title={'Filter'} placement={'top'}>
<Button variant={'outline'} className="h-7.5 disabled:bg-gray-400">
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip>
</div>
<div className="flex gap-3 items-center">
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleAddDialog(true)}
>
Add Data
</Button>
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export default ListToolbar;

View File

@ -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<ContextProps>(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<string | null>(null);
const [accessTypes, setAccessTypes] = useState<AccessTypeProps[]>([]);
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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
id: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[100px]'
}
},
{
accessorFn: (row) => row.name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.internal_name,
id: 'internal_name',
header: ({ column }) => <DataGridColumnHeader title="Internal Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.description,
id: 'description',
header: ({ column }) => <DataGridColumnHeader title="Description" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
id: 'actions',
enableSorting: false,
header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
cell: ({ row }) => {
return (
<>
<button
className="btn btn-sm btn-primary mr-2"
onClick={() => handleEditDialog(true, row.original.id)}
>
Edit
</button>
<button className="btn btn-sm btn-primary" onClick={() => handleAddDialog(true)}>
Add
</button>
</>
);
},
meta: {
headerClassName: 'w-[100px]',
cellClassName: 'text-center'
}
}
],
[handleAddDialog, handleEditDialog]
);
return (
<div>
<div className="container mx-auto py-5">
<h1>Manage Access Type</h1>
</div>
<ManageAccessTypeContext.Provider
value={{
showEditDialog,
handleEditDialog,
showAddDialog,
handleAddDialog,
selectedUser,
accessTypes
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
layout={{ card: true }}
toolbar={<ListToolbar />}
sorting={[{ id: 'id', desc: true }]}
serverSide={true}
>
{children}
</DataGridProvider>
</ManageAccessTypeContext.Provider>
</div>
);
};
export { ManageAccessTypeContext, ManageAccessTypeContextProvider };
export type { SelectedUser };

View File

@ -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 };

View File

@ -0,0 +1,21 @@
import MemberCredentialsForm from './blocks/MemberCredentialsForm';
import { useMemberCredentials } from './hooks';
const MemberCredentials = () => {
const { error, memberCredentials, handleChange, handleSubmit } = useMemberCredentials();
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Member Credential</h1>
<MemberCredentialsForm
form={memberCredentials}
error={error}
onChange={handleChange}
onSubmit={handleSubmit}
/>
</div>
</div>
);
};
export default MemberCredentials;

View File

@ -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 (
<div className="max-w-md mx-auto bg-white p-6 rounded-lg shadow-md">
<h2 className="text-xl font-bold mb-4">Create Member Credentials</h2>
{error && (
<Alert variant="danger" className="mb-5">
{error}
</Alert>
)}
<div className="space-y-4">
{/* Access Type */}
<div>
<label>
<span className="text-red-500">*</span>Access Type
</label>
<Select onValueChange={(value) => onChange('accessType', value)}>
<SelectTrigger>
<SelectValue placeholder="Select Access Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Pin Credentials">Pin Credentials</SelectItem>
<SelectItem value="Secret Auth">Secret Auth</SelectItem>
<SelectItem value="APIkey">APIkey</SelectItem>
<SelectItem value="Web Credentials">Web Credentials</SelectItem>
<SelectItem value="OTM Tpay">OTM Tpay</SelectItem>
</SelectContent>
</Select>
</div>
{/* Username */}
<div>
<label>
<span className="text-red-500">*</span>Username
</label>
<Input value={form.username} onChange={(e) => onChange('username', e.target.value)} />
</div>
{/* Credentials */}
<div>
<label>
<span className="text-red-500">*</span>Credentials
</label>
<Input
type="password"
value={form.credential}
onChange={(e) => onChange('credential', e.target.value)}
/>
</div>
{/* Confirm Credentials */}
<div>
<label>
<span className="text-red-500">*</span>Confirm Credentials
</label>
<Input
type="password"
value={form.confirmCredential}
onChange={(e) => onChange('confirmCredential', e.target.value)}
/>
</div>
<Button className="w-full" onClick={onSubmit}>
Create Account
</Button>
</div>
</div>
);
};
export default MemberCredentialsForm;

View File

@ -0,0 +1 @@
export * from './useMemberCredentials';

View File

@ -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<MemberCredentials>({
accessType: null,
username: '',
credential: '',
confirmCredential: ''
});
const [error, setError] = useState<string | null>(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 };
};

View File

@ -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<Account>[] = [
{
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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(dataAccount.id.toString())}
>
Copy account ID
</DropdownMenuItem>
{/* <DropdownMenuSeparator /> */}
</DropdownMenuContent>
</DropdownMenu>
);
}
}
];

View File

@ -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<Account[]>([]);
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 (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900 p-5">Manage Account</h1>
<DataTable columns={columns} data={dataAccount} />
</div>
</div>
);
};
export default ManageAccount;

View File

@ -0,0 +1,3 @@
import { apiConfig } from '@/config/api.config';
const API_URL = apiConfig.service_dashboard;

View File

@ -0,0 +1,18 @@
import { Container, DataGridInner } from '@/components';
import { ManageCurrencyContextProvider } from './hooks/ManageCurrencyContext';
import AddDialog from './blocks/AddDialog';
const ManageCurrency = () => {
return (
<ManageCurrencyContextProvider>
<Container>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
<AddDialog />
</Container>
</ManageCurrencyContextProvider>
);
};
export default ManageCurrency;

View File

@ -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<any | null>(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<HTMLFormElement>) => {
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 (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
<DialogHeader className="p-5 border-0">
<div className="flex items-center justify-between flex-wrap grow">
<div className="flex flex-col justify-center">
<h1 className="text-xl font-semibold leading-none text-gray-900">Create Currency</h1>
<div className="flex items-center gap-2 text-sm font-normal text-gray-700"></div>
</div>
<div
className="cursor-pointer hover:opacity-100 opacity-50"
onClick={() => {
handleAddDialog(false);
resetForm();
}}
>
<KeenIcon icon="cross" className="text-1.5xl" />
</div>
</div>
</DialogHeader>
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
<div className="flex flex-col px-0">
{alert.show && (
<Alert variant="danger" className="mb-5">
{alert.message}
</Alert>
)}
<form action="" onSubmit={handleSubmit}>
<div className="card-body grid gap-5 p-0">
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Name<span className="text-red-500">*</span>
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.name}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, name: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Code<span className="text-red-500">*</span>
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.code}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, code: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Prefix<span className="text-red-500">*</span>
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.prefix}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, prefix: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">Trailer</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.trailer}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, trailer: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Format<span className="text-red-500">*</span>
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.format}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, format: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Grouping Separator<span className="text-red-500">*</span>
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.grouping_separator}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, grouping_separator: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Decimal Separator<span className="text-red-500">*</span>
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.decimal_separator}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, decimal_separator: target.value }))
}
/>
</div>
</div>
<div className="flex justify-end pt-2.5 gap-5">
<Button variant={'outline'} type="reset" onClick={handleReset}>
Reset
</Button>
<Button variant={'default'} type="submit">
Save Changes
</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default AddDialog;

View File

@ -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 (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search Currency"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/>
</label>
<DefaultTooltip title={'Filter'} placement={'top'}>
<Button variant={'outline'} className="h-7.5 disabled:bg-gray-400">
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip>
</div>
<div className="flex gap-3 items-center">
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleAddDialog(true)}
>
Add Data
</Button>
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export default ListToolbar;

View File

@ -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<ContextProps>(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<string | null>(null);
const [currencies, setCurrencies] = useState<CurrencyProps[]>([]);
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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
id: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[100px]'
}
},
{
accessorFn: (row) => row.name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.code,
id: 'code',
header: ({ column }) => <DataGridColumnHeader title="Code" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.prefix,
id: 'prefix',
header: ({ column }) => <DataGridColumnHeader title="Prefix" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.trailer,
id: 'trailer',
header: ({ column }) => <DataGridColumnHeader title="Trailer" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.format,
id: 'format',
header: ({ column }) => <DataGridColumnHeader title="Format" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.grouping_separator,
id: 'grouping_separator',
header: ({ column }) => <DataGridColumnHeader title="Grouping Separator" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.decimal_separator,
id: 'decimal_separator',
header: ({ column }) => <DataGridColumnHeader title="Decimal Separator" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
id: 'actions',
enableSorting: false,
header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
cell: ({ row }) => {
return (
<>
<button
className="btn btn-sm btn-primary mr-2"
onClick={() => handleEditDialog(true, row.original.id)}
>
Edit
</button>
</>
);
},
meta: {
headerClassName: 'w-[100px]',
cellClassName: 'text-center'
}
}
],
[handleAddDialog, handleEditDialog]
);
console.log(currencies);
return (
<div>
<div className="container mx-auto py-5">
<h1>Manage Currency</h1>
</div>
<ManageCurrencyContext.Provider
value={{
showEditDialog,
handleEditDialog,
showAddDialog,
handleAddDialog,
selectedUser,
currencies
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
{currencies.length > 0 ? (
<DataGridProvider
columns={columns}
data={currencies}
pagination={{ size: 10 }}
layout={{ card: true }}
toolbar={<ListToolbar />}
sorting={[{ id: 'id', desc: true }]}
serverSide={true}
>
{children}
</DataGridProvider>
) : (
<p>loading</p>
)}
</ManageCurrencyContext.Provider>
</div>
);
};
export { ManageCurrencyContext, ManageCurrencyContextProvider };
export type { SelectedUser };

View File

@ -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 };

View File

@ -14,6 +14,8 @@ import { useFetchCardData, useFetchChartData } from './hooks';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useFetchYear } from './hooks/useFetchYear'; import { useFetchYear } from './hooks/useFetchYear';
import { get5LastYear } from '@/utils/Date'; import { get5LastYear } from '@/utils/Date';
import { staticChartData } from './staticChart';
// sum -> nominal, count-> total // sum -> nominal, count-> total
type CountType = 'sum' | 'count'; type CountType = 'sum' | 'count';
type ChartType = 'line' | 'bar'; type ChartType = 'line' | 'bar';
@ -184,9 +186,9 @@ const DashboardHomePage = () => {
<div className="lg:col-span-1"> <div className="lg:col-span-1">
<Chart <Chart
title="Overview" title="Overview"
count={count} count={12}
toolbar={toolbar} toolbar={toolbar}
chartData={chartData} chartData={staticChartData.series.map((data: any) => data.data)}
chartType={chartType} chartType={chartType}
chartLegend={chartLegend} chartLegend={chartLegend}
/> />

View File

@ -37,7 +37,7 @@ const Chart = ({
plotOptions: { plotOptions: {
bar: { bar: {
horizontal: false, horizontal: false,
columnWidth: '50%' columnWidth: '30%'
} }
}, },
dataLabels: { dataLabels: {

View File

@ -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']
}
};

View File

@ -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<Group>[] = [
{
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'
// }
];

View File

@ -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<HTMLInputElement>) => {
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 (
<div>
<div className="container mx-auto pt-2">
<ConfirmDialog
open={dialogOpen}
onClose={() => setDialogOpen(false)}
title="Confirm Action"
content={`Are you sure you want to `+( dialogType === 'create' ? "create?" : ( dialogType === 'update' ? "update?" : "delete?"))}
onYes={handleYes}
onNo={handleNo}
/>
<h1 className="text-xl font-medium leading-none text-gray-900 p-5">Groups</h1>
<DataTable data={dataGroup} columns={columns} createGroup={createGroup} onUpdate={handleUpdate} onDelete={handleDelete} />
<Dialog open={isDialogOpen} onClose={setIsDialogOpen}>
<DialogContent className="w-[600px]">
<div className="flex justify-between">
<DialogTitle>Create New Group</DialogTitle>
<Box display="flex" justifyContent="flex-end">
<Button variant="outlined" sx={{ borderColor: "white", color: 'grey' }} onClick={closeDialog}><CloseIcon/></Button>
</Box>
</div>
<Divider/>
<div className="p-5 mt-5">
<form onSubmit={handleSubmit} className="flex flex-col gap-4 w-full">
<div className="grid grid-cols-4 items-center gap-4 w-full">
<label className="form-label text-sm">
<span className="text-red-500">*</span>Group Name:
</label>
<input
type="text"
name="groupName"
className="input w-full col-span-3"
value={formData.groupName}
onChange={handleChange}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4 w-full">
<label className="form-label text-sm">
<span className="text-red-500">*</span>Active Status:
</label>
<FormControl>
<RadioGroup name='status' row value={formData.status} onChange={handleChange}>
<FormControlLabel value="Y" checked={formData.status === 'Y'} control={<Radio />} label="Yes" />
<FormControlLabel value="N" checked={formData.status === 'N'} control={<Radio />} label="No" />
</RadioGroup>
</FormControl>
</div>
<div className="grid grid-cols-4 items-center gap-4 w-full">
<label className="form-label text-sm">
<span className="text-red-500">*</span>Description:
</label>
<input
type="text"
name="description"
className="input w-full col-span-3"
value={formData.description}
onChange={handleChange}
/>
</div>
{/* <div className="grid grid-cols-4 items-center gap-4 w-full">
<label className="form-label text-sm">
<span className="text-red-500">*</span>PIN Length:
</label>
<input
type="text"
name="pin_length"
className="input w-full col-span-3"
value={formData.pin_length}
onChange={handleChange}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4 w-full">
<label className="form-label text-sm">
<span className="text-red-500">*</span>Max Pin Attempts:
</label>
<input
type="text"
name="max_pin_attempts"
className="input w-full col-span-3"
value={formData.max_pin_attempts}
onChange={handleChange}
/>
</div> */}
{/* <div className="grid grid-cols-4 items-center gap-4 w-full mb-5">
<label className="form-label text-sm">Default Notification:</label>
<Select
onValueChange={(e) => setFormData({ ...formData, default_notification: e })}
>
<SelectTrigger className="w-full col-span-3">
<SelectValue placeholder="Select Default Notification" />
</SelectTrigger>
<SelectContent className="w-full">
<SelectItem value="1 - notifSender">1 - notifSender</SelectItem>
<SelectItem value="2 - notifBenefeciary">2 - notifBenefeciary</SelectItem>
</SelectContent>
</Select>
<input
type="text"
name="default_notification"
className="input w-full col-span-3"
value={formData.default_notification}
onChange={handleChange}
/>
</div> */}
<Button type="submit">Submit</Button>
</form>
</div>
</DialogContent>
</Dialog>
</div>
</div>
);
};
export default ManageGroups;

View File

@ -0,0 +1,11 @@
const MasterData = () => {
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Master Data Page</h1>
</div>
</div>
);
};
export default MasterData;

View File

@ -0,0 +1,11 @@
const AldeiasMaster = () => {
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Aldeias Master Data</h1>
</div>
</div>
);
};
export default AldeiasMaster;

View File

@ -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 (
<ManageMunicipiosProvider>
<Container className="mb-7">
<h1 className="text-xl font-medium leading-none text-gray-900 mb-5">MUNICIPIOS</h1>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
<AddDialog />
<SearchDialog />
</Container>
</ManageMunicipiosProvider>
);
};
export default Municipios;

View File

@ -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<any | null>(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<HTMLFormElement>) => {
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 (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-96 flex flex-col p-5 overflow-hidden [&>button]:hidden">
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
<DialogHeader className="p-2 border-0">
<div className="flex items-center justify-between flex-wrap grow">
<div className="flex flex-col justify-center">
<h1 className="text-xl font-semibold leading-none text-gray-900">Add Municipios</h1>
<div className="flex items-center gap-2 text-sm font-normal text-gray-700"></div>
</div>
<div
className="cursor-pointer hover:opacity-100 opacity-50"
onClick={() => {
handleAddDialog(false);
resetForm();
}}
>
<KeenIcon icon="cross" className="text-1.5xl" />
</div>
</div>
</DialogHeader>
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
<div className="flex flex-col px-0">
{alert.show && (
<Alert variant="danger" className="mb-5">
{alert.message}
</Alert>
)}
<form action="" onSubmit={handleSubmit}>
<div className="card-body grid-cols-6 gap-5 p-0">
<div className="grid grid-cols-8 gap-2 w-full items-center">
<label className="form-label flex items-center gap-1 col-span-2">
Name<span className="text-red-500">*</span>
</label>
<Input
className="input col-span-6"
type="text"
autoComplete="off"
value={formField.name}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, name: target.value }))
}
/>
</div>
<div className="flex justify-end pt-2.5 gap-5 col-span-6">
<Button variant={'outline'} type="reset" onClick={handleReset}>
Reset
</Button>
<Button variant={'default'} type="submit">
Save Changes
</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default AddDialog;

View File

@ -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 (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search Municipios"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/>
</label>
<DefaultTooltip title={'Filter'} placement={'top'}>
<Button
variant="outline"
className="h-7.5 disabled:bg-gray-400"
// disabled={isLoading}
// onClick={handleFilterData}
>
{/* {loadingButton === 'filter' ? <ContentLoader /> : <KeenIcon icon="filter" />} */}
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip>
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleSearchDialog(true)}
>
Search Postu Administrativo
</Button>
</div>
<div className="flex gap-3 items-center">
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleAddDialog(true)}
>
Add Data
</Button>
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export default ListToolbar;

View File

@ -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<any | null>(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<PostoAdms[]>([]);
const [isFound, setIsFound] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
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 (
<Dialog open={showSearchDialog} onOpenChange={(open) => handleSearchDialog(open)}>
<DialogContent className="container-fixed max-w-96 flex flex-col p-5 overflow-hidden [&>button]:hidden">
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
<DialogHeader className="p-2 border-0">
<div className="flex items-center justify-between flex-wrap grow">
<div className="flex flex-col justify-center">
<h1 className="text-xl font-semibold leading-none text-gray-900">
Search Postu Administravo
</h1>
<div className="flex items-center gap-2 text-sm font-normal text-gray-700"></div>
</div>
<div
className="cursor-pointer hover:opacity-100 opacity-50"
onClick={() => {
handleSearchDialog(false);
resetForm();
}}
>
<KeenIcon icon="cross" className="text-1.5xl" />
</div>
</div>
</DialogHeader>
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
<div className="flex flex-col px-0">
{alert.show && (
<Alert variant="danger" className="mb-5">
{alert.message}
</Alert>
)}
<form action="" onSubmit={handleSubmit}>
<div className="card-body grid-cols-6 gap-5 p-0">
<div className="grid grid-cols-8 gap-2 w-full items-center">
<label className="form-label flex items-center gap-1 col-span-2">
Name<span className="text-red-500">*</span>
</label>
<Select
value={formField.id.toString()}
onValueChange={(target) => {
const selectedMunicipio = municipios.find((m) => m.id.toString() === target);
if (selectedMunicipio) {
setFormField({
...formField,
id: selectedMunicipio.id,
name: selectedMunicipio.name
});
}
}}
>
<SelectTrigger className="col-span-6">
<SelectValue placeholder="Select Municipios" />
</SelectTrigger>
<SelectContent>
{municipios.map((municipio) => (
<SelectItem key={municipio.id} value={municipio.id.toString()}>
{municipio.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{isFound && postoadms.length > 0 && (
<div className="mt-4 border-t pt-4">
<h2 className="text-md font-semibold">Postu Administravo: </h2>
<br />
<div className="flex flex-col">
<span className="text-sm form-hint">
{postoadms.map((posto) => posto.name).join(', ')}
</span>
</div>
</div>
)}
<div className="flex justify-end pt-2.5 gap-5 col-span-6">
<Button variant={'outline'} type="reset" onClick={handleReset}>
Reset
</Button>
<Button variant={'default'} type="submit">
Search
</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default SearchDialog;

View File

@ -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<void>;
// getMunicipiosByName: (name: string) => Promise<void>;
// createMunicipios: (data: Partial<MunicipiosProps>) => Promise<void>;
// updateMunicipios: (id: number, data: Partial<MunicipiosProps>) => Promise<void>;
// deleteMunicipios: (id: number, hardDelete?: boolean) => Promise<void>;
// restoreMunicipios: (id: number) => Promise<void>;
// }
const ManageMunicipiosContext = createContext<ContextProps>(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<string | null>(null);
const [municipios, setMunicipios] = useState<MunicipiosProps[]>([]);
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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
id: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[100px]'
}
},
{
accessorFn: (row) => row.name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Municipios Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[1000px]'
}
},
{
id: 'actions',
header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[100px], text-center',
cellClassName: 'text-center'
},
cell: (info) => (
<Button
variant={'outline'}
onClick={() => navigate(`/master-data/municipios/postoadms/${info.row.original.id}`)}
>
Details
</Button>
)
}
],
[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<MunicipiosProps>) => {
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<MunicipiosProps>) => {
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 (
<ManageMunicipiosContext.Provider
value={{
municipios,
showSearchDialog,
handleSearchDialog,
showAddDialog,
handleAddDialog,
showDeleteDialog,
handleDeleteDialog,
showEditDialog,
handleEditDialog,
selectedMunicipios,
getMunicipiosLists
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 25 }}
toolbar={<ListToolbar />}
layout={{ card: true }}
sorting={[{ id: 'id', desc: false }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getMunicipiosLists(pageIndex, pageSize, sorting, columnFilters)
}
>
{children}
</DataGridProvider>
</ManageMunicipiosContext.Provider>
);
};
export { ManageMunicipiosProvider, ManageMunicipiosContext };
export type { MunicipiosProps };

View File

@ -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 };

View File

@ -0,0 +1,17 @@
import { ManagePostoAdmsContextProvider } from './hooks/ManagePostoAdmsContext';
import { Container, DataGridInner } from '@/components';
const PostoAdmsMaster = () => {
return (
<ManagePostoAdmsContextProvider>
<Container>
<h1>Postu Administrativo</h1>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
</Container>
</ManagePostoAdmsContextProvider>
);
};
export default PostoAdmsMaster;

View File

@ -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 (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search Postu Administrativo"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/>
</label>
<DefaultTooltip title={'Filter'} placement={'top'}>
<Button
variant="outline"
className="h-7.5 disabled:bg-gray-400"
// disabled={isLoading}
// onClick={handleFilterData}
>
{/* {loadingButton === 'filter' ? <ContentLoader /> : <KeenIcon icon="filter" />} */}
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip>
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleSearchDialog(true)}
>
Search Postu Administrativo
</Button>
</div>
<div className="flex gap-3 items-center">
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleAddDialog(true)}
>
Add Data
</Button>
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export default ListToolbar;

View File

@ -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<ContextProps>(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<string | null>(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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
id: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[100px]'
}
},
{
accessorFn: (row) => row.name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Municipios Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[1000px]'
}
},
{
id: 'actions',
header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[100px], text-center',
cellClassName: 'text-center'
},
cell: (info) => (
<Button
variant={'outline'}
onClick={() => navigate(`/master-data/municipios/postoadms/${info.row.original.id}`)}
>
Details
</Button>
)
}
],
[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 (
<ManagePostoAdmsContext.Provider
value={{
showSearchDialog,
handleSearchDialog,
showAddDialog,
handleAddDialog,
showEditDialog,
handleEditDialog,
showDeleteDialog,
handleDeleteDialog,
selectedPostoAdms,
getPostoAdmsLists
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 25 }}
toolbar={<ListToolbar />}
layout={{ card: true }}
sorting={[{ id: 'id', desc: false }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getPostoAdmsLists(pageIndex, pageSize, sorting, columnFilters)
}
>
{children}
</DataGridProvider>
</ManagePostoAdmsContext.Provider>
);
};
export { ManagePostoAdmsContextProvider, ManagePostoAdmsContext };
export type { PostoAdmsProps };

View File

@ -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 };

View File

@ -0,0 +1,11 @@
const SucosMaster = () => {
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Sucos Master Data</h1>
</div>
</div>
);
};
export default SucosMaster;

View File

@ -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<TData extends object> {
props: TDataGridProps<TData>;
table: Table<TData>;
totalRows: number;
loading: (state: boolean) => void;
reload: () => void;
children?: React.ReactNode;
}
const DataGridContext = createContext<IDataGridContextProps<any> | 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 (
<div>
<div className="container mx-auto py-5">
<h1 className="text-xl font-medium leading-none text-gray-900 mb-7">Manage Member KYC</h1>
<ManageKycContextProvider>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
</ManageKycContextProvider>
</div>
</div>
);
};
export default Kyc;

View File

@ -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<any | null>(null);
const { showAddDialog, handleAddDialog } = useKycContext();
const { reload } = useDataGrid();
return <Dialog></Dialog>;
};
export default AddDialog;

View File

@ -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 (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search users"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/>
</label>
<DefaultTooltip title={'Filter'} placement={'top'}>
<Button
variant="outline"
className="h-7.5 disabled:bg-gray-400"
// disabled={isLoading}
// onClick={handleFilterData}
>
{/* {loadingButton === 'filter' ? <ContentLoader /> : <KeenIcon icon="filter" />} */}
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip>
</div>
<div className="flex gap-3 items-center">
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export { ListToolBar };

View File

@ -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<ContextProps>(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<SelectedUser | null>(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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
id: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[100px]'
}
},
{
accessorFn: (row) => row.created_date,
id: 'created_date',
header: ({ column }) => <DataGridColumnHeader title="Created Date" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[350px]'
}
},
{
accessorFn: (row) => row.description,
id: 'description',
header: ({ column }) => <DataGridColumnHeader title="Description" column={column} />,
enableSorting: true,
meta: {
headerClassName: 'w-[350px]'
}
},
{
id: 'actions',
enableSorting: false,
header: ({ column }) => <DataGridColumnHeader title="Action" column={column} />,
cell: (data: any) => {
const row = data.row.original;
return (
<>
<button
className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => handleDetailDialog(true, row.id)}
>
<KeenIcon icon="notepad-edit" />
</button>
</>
);
},
meta: {
headerClassName: 'w-[100px]',
cellClassName: 'text-center'
}
}
],
[handleDetailDialog]
);
return (
<ManageKycContext.Provider
value={{
showDetailDialog,
handleDetailDialog,
showAddDialog,
handleAddDialog,
selectedUser
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
toolbar={<ListToolBar />}
layout={{ card: true }}
sorting={[{ id: 'name', desc: false }]}
serverSide={true}
>
{children}
</DataGridProvider>
</ManageKycContext.Provider>
);
};
export { ManageKycContextProvider, ManageKycContext };
export type { SelectedUser };

View File

@ -0,0 +1,2 @@
export * from './ManageKycContext';
export * from './useKycContext';

View File

@ -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 };

View File

@ -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<Members>[] = [
{
accessorKey: 'id',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
ID
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
}
},
{
accessorKey: 'username',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Username
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
}
},
{
accessorKey: 'name',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
}
},
{
accessorKey: 'email',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Email
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
}
},
{
accessorKey: 'createdDate',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Created Date
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => new Date(row.original.createdDate).toLocaleDateString()
},
{
id: 'actions',
cell: ({ row }) => {
const dataMembers = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(dataMembers.id.toString())}
>
Copy account ID
</DropdownMenuItem>
{/* <DropdownMenuSeparator /> */}
</DropdownMenuContent>
</DropdownMenu>
);
}
}
];

View File

@ -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 (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Manage Members</h1>
<DataTable data={dataMembers} columns={columns} />
</div>
</div>
);
};
export default ManageMembers;

View File

@ -0,0 +1,11 @@
const ManageMenu = () => {
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Manage Menu</h1>
</div>
</div>
);
};
export default ManageMenu;

View File

@ -0,0 +1,11 @@
const MenuCategory = () => {
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Menu Category</h1>
</div>
</div>
);
};
export default MenuCategory;

View File

@ -0,0 +1,11 @@
const Welcome = () => {
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Welcome</h1>
</div>
</div>
);
};
export default Welcome;

View File

@ -0,0 +1,11 @@
const Inbox = () => {
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Inbox</h1>
</div>
</div>
);
};
export default Inbox;

View File

@ -0,0 +1,18 @@
import { Container, DataGridInner } from '@/components';
import { ManageNotifContextProvider } from './hooks/ManageNotificationContext';
import AddDialog from './blocks/AddDialog';
const ManageNotification = () => {
return (
<ManageNotifContextProvider>
<Container>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
<AddDialog />
</Container>
</ManageNotifContextProvider>
);
};
export default ManageNotification;

View File

@ -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<any | null>(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<HTMLFormElement>) => {
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 (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
<DialogHeader className="p-5 border-0">
<div className="flex items-center justify-between flex-wrap grow">
<div className="flex flex-col justify-center">
<h1 className="text-xl font-semibold leading-none text-gray-900">
Create Notification
</h1>
<div className="flex items-center gap-2 text-sm font-normal text-gray-700"></div>
</div>
<div
className="cursor-pointer hover:opacity-100 opacity-50"
onClick={() => {
handleAddDialog(false);
resetForm();
}}
>
<KeenIcon icon="cross" className="text-1.5xl" />
</div>
</div>
</DialogHeader>
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
<div className="flex flex-col px-0">
{alert.show && (
<Alert variant="danger" className="mb-5">
{alert.message}
</Alert>
)}
<form action="" onSubmit={handleSubmit}>
<div className="card-body grid gap-5 p-0">
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Name<span className="text-red-500">*</span>
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.name}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, name: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Destination Module<span className="text-red-500">*</span>
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.destination_module}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, destination_module: target.value }))
}
/>
</div>
</div>
<div className="flex justify-end pt-2.5 gap-5">
<Button variant={'outline'} type="reset" onClick={handleReset}>
Reset
</Button>
<Button variant={'default'} type="submit">
Save Changes
</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default AddDialog;

View File

@ -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 (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search users"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) =>
table.getColumn('name')?.setFilterValue(event.target.value)
}
/>
</label>
<DefaultTooltip title={'Filter'} placement={'top'}>
<Button
variant="outline"
className="h-7.5 disabled:bg-gray-400"
// disabled={isLoading}
// onClick={handleFilterData}
>
{/* {loadingButton === 'filter' ? <ContentLoader /> : <KeenIcon icon="filter" />} */}
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip>
</div>
<div className="flex gap-3 items-center">
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleAddDialog(true)}
>
Add Data
</Button>
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export { ListToolBar };

View File

@ -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<ContextProps>(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<string | null>(null);
const [notifications, setNotifications] = useState<NotificationProps[]>([]);
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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
id: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[100px]'
}
},
{
accessorFn: (row) => row.name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Name" column={column} />,
enableSorting: true,
enableHiding: false
},
{
accessorFn: (row) => row.destination_module,
id: 'destination_module',
header: ({ column }) => <DataGridColumnHeader title="Destination Module" column={column} />,
enableSorting: true,
enableHiding: false
},
{
id: 'actions',
header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
meta: {
headerClassName: 'w-[100px]',
cellClassName: 'text-center'
},
cell: (data: any) => {
const row = data.row.original;
return (
<div className="flex justify-center gap-2">
<button
type="button"
className="flex items-center justify-center gap-2 text-sm font-medium leading-6 text-primary"
onClick={() => handleEditDialog(true, row.id)}
>
<span>Edit</span>
</button>
</div>
);
}
}
],
[handleEditDialog, handleAddDialog]
);
return (
<div>
<div className="container mx-auto py-5">
<h1>Manage Notifications</h1>
</div>
<ManageNotifContext.Provider
value={{
handleAddDialog,
showAddDialog,
handleEditDialog,
showEditDialog,
selectedNotification,
notifications
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
toolbar={<ListToolBar />}
layout={{ card: true }}
sorting={[{ id: 'username', desc: false }]}
serverSide={true}
>
{children}
</DataGridProvider>
</ManageNotifContext.Provider>
</div>
);
};
export { ManageNotifContext, ManageNotifContextProvider };
export type { SelectedNotification };

View File

@ -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 };

View File

@ -336,6 +336,7 @@ const AddDialog = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="flex justify-end pt-2.5"> <div className="flex justify-end pt-2.5">
<Button className="btn btn-primary" type="submit" disabled={isButtonDisabled}> <Button className="btn btn-primary" type="submit" disabled={isButtonDisabled}>

View File

@ -0,0 +1,21 @@
import { Container, DataGridInner } from '@/components';
import {
ManageTransferTypeContext,
ManageTransferTypeContextProvider
} from './hooks/ManageTransferTypeContext';
import AddDialog from './blocks/AddDialog';
const TransferType = () => {
return (
<ManageTransferTypeContextProvider>
<Container>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
<AddDialog />
</Container>
</ManageTransferTypeContextProvider>
);
};
export default TransferType;

View File

@ -0,0 +1,294 @@
import { apiConfig } from '@/config/api.config';
import { useRef, useState } from 'react';
import { useManageTransferTypeContext } from '../hooks/useManageTransferTypeContext';
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 {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select';
import { Button } from '@/components/ui/button';
interface CreateTransferTypeParams {
transfer_type_name: string;
description: string;
from_account: string;
to_account: string;
minimal_amount: number;
maximum_amount: number;
otp_threshold: number;
maximum_transaction_perDay: number;
}
const API_URL = apiConfig.service_dashboard;
const AddDialog = () => {
const parentRef = useRef<any | null>(null);
const { showAddDialog, handleAddDialog, accounts } = useManageTransferTypeContext();
const { reload } = useDataGrid();
const { PostData, PutData } = useCallApi();
const [alert, setAlert] = useState({
show: false,
message: ''
});
const initialState = {
transfer_type_name: '',
description: '',
from_account: '',
to_account: '',
minimal_amount: 0,
maximum_amount: 0,
otp_threshold: 0,
maximum_transaction_perDay: 0
};
const [formField, setFormField] = useState(initialState);
const resetForm = () => {
setFormField(initialState);
};
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// setIsSubmitting(true);
const payload = {
transfer_type_name: formField.transfer_type_name,
description: formField.description,
from_account: formField.from_account,
to_account: formField.to_account,
minimal_amount: formField.minimal_amount,
maximum_amount: formField.maximum_amount,
otp_threshold: formField.otp_threshold,
maximum_transaction_perDay: formField.maximum_transaction_perDay
};
console.log(payload);
};
return (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
<DialogHeader className="p-5 border-0">
<div className="flex items-center justify-between flex-wrap grow">
<div className="flex flex-col justify-center">
<h1 className="text-xl font-semibold leading-none text-gray-900">
Create Transfer Type
</h1>
<div className="flex items-center gap-2 text-sm font-normal text-gray-700"></div>
</div>
<div
className="cursor-pointer hover:opacity-100 opacity-50"
onClick={() => {
handleAddDialog(false);
resetForm();
}}
>
<KeenIcon icon="cross" className="text-1.5xl" />
</div>
</div>
</DialogHeader>
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
<div className="flex flex-col px-0">
{alert.show && (
<Alert variant="danger" className="mb-3">
<h3>{alert.message}</h3>
</Alert>
)}
<form action="" onSubmit={handleSubmit}>
<div className="card-body grid gap-5 p-0">
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Transfer Type Name
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.transfer_type_name}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, transfer_type_name: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Description
</label>
<Input
className="input"
type="text"
autoComplete="off"
value={formField.description}
onChange={({ target }) =>
setFormField((prev) => ({ ...prev, description: target.value }))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">From Account</label>
<div className="grow">
<Select
value={formField.from_account}
onValueChange={(target) =>
setFormField((prev) => ({ ...prev, from_account: target }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
{accounts.map((account, idx) => (
<SelectItem value={account.name} key={account.id}>
{account.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="w-full">
<div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">To Account</label>
<div className="grow">
<Select
value={formField.to_account}
onValueChange={(target) =>
setFormField((prev) => ({ ...prev, to_account: target }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
{accounts.map((account, idx) => (
<SelectItem value={account.name} key={account.id}>
{account.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Minimal Amount
</label>
<Input
className="input"
type="number"
autoComplete="off"
value={formField.minimal_amount}
onChange={({ target }) =>
setFormField((prev) => ({
...prev,
minimal_amount: Number(target.value)
}))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Maximum Amount
</label>
<Input
className="input"
type="number"
autoComplete="off"
value={formField.maximum_amount}
onChange={({ target }) =>
setFormField((prev) => ({
...prev,
maximum_amount: Number(target.value)
}))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
OTP Threshold
</label>
<Input
className="input"
type="number"
autoComplete="off"
value={formField.otp_threshold}
onChange={({ target }) =>
setFormField((prev) => ({
...prev,
otp_threshold: Number(target.value)
}))
}
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Maximum Transaction / day
</label>
<Input
className="input"
type="number"
autoComplete="off"
value={formField.maximum_transaction_perDay}
onChange={({ target }) =>
setFormField((prev) => ({
...prev,
maximum_transaction_perDay: Number(target.value)
}))
}
/>
</div>
</div>
<div className="flex justify-end pt-2.5 gap-5">
<Button variant={'outline'} type="reset">
Reset
</Button>
<Button variant={'default'} type="submit">
Save Changes
</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default AddDialog;

View File

@ -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 (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search Access Type"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/>
</label>
<DefaultTooltip title={'Filter'} placement={'top'}>
<Button variant={'outline'} className="h-7.5 disabled:bg-gray-400">
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip>
</div>
<div className="flex gap-3 items-center">
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleAddDialog(true)}
>
Add Data
</Button>
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export default ListToolbar;

View File

@ -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<ContextProps>(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<string | null>(null);
const [accounts, setAccount] = useState<AccountProps[]>([]);
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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.id,
id: 'id',
header: ({ column }) => <DataGridColumnHeader title="ID" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[100px]'
}
},
{
accessorFn: (row) => row.name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.internal_name,
id: 'internal_name',
header: ({ column }) => <DataGridColumnHeader title="Internal Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.description,
id: 'description',
header: ({ column }) => <DataGridColumnHeader title="Description" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
id: 'actions',
enableSorting: false,
header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
cell: ({ row }) => {
return (
<>
<button
className="btn btn-sm btn-primary mr-2"
onClick={() => handleEditDialog(true, row.original.id)}
>
Edit
</button>
<button className="btn btn-sm btn-primary" onClick={() => handleAddDialog(true)}>
Add
</button>
</>
);
},
meta: {
headerClassName: 'w-[100px]',
cellClassName: 'text-center'
}
}
],
[handleAddDialog, handleEditDialog]
);
return (
<div>
<div className="container mx-auto py-5">
<h1>Manage Access Type</h1>
</div>
<ManageTransferTypeContext.Provider
value={{
showEditDialog,
handleEditDialog,
showAddDialog,
handleAddDialog,
selectedUser,
accounts
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
layout={{ card: true }}
toolbar={<ListToolbar />}
sorting={[{ id: 'id', desc: true }]}
serverSide={true}
>
{children}
</DataGridProvider>
</ManageTransferTypeContext.Provider>
</div>
);
};
export { ManageTransferTypeContext, ManageTransferTypeContextProvider };
export type { SelectedUser };

View File

@ -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 };

View File

@ -0,0 +1,11 @@
const ManageWebServices = () => {
return (
<div>
<div className="container mx-auto p-5">
<h1 className="text-xl font-medium leading-none text-gray-900">Manage Web Services</h1>
</div>
</div>
);
};
export default ManageWebServices;

View File

@ -11,6 +11,25 @@ import ManageUserPage from '@/pages/settings/user/manage-user/ManageUserPage';
import LogActivityPage from '@/pages/settings/user/log-activity/LogActivityPage'; import LogActivityPage from '@/pages/settings/user/log-activity/LogActivityPage';
import ManagePositionPage from '@/pages/settings/user/manage-position/ManagePositionPage'; 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 => { const AppRoutingSetup = (): ReactElement => {
return ( return (
@ -18,8 +37,40 @@ const AppRoutingSetup = (): ReactElement => {
<Route element={<RequireAuth />}> <Route element={<RequireAuth />}>
<Route element={<Demo2Layout />}> <Route element={<Demo2Layout />}>
<Route path="/" element={<DashboardHomePage />} /> <Route path="/" element={<DashboardHomePage />} />
<Route path="/master-data" element={<MasterData />} />
<Route path="/master-data/municipios" element={<Municipios />} />
<Route path="/master-data/postoadms" element={<PostoAdmsMaster />} />
<Route path="/master-data/sucos" element={<SucosMaster />} />
<Route path="/master-data/aldeias" element={<AldeiasMaster />} />
<Route path="/master-data/municipios/postoadms/:municipioId" element={<PostoAdmsMaster />} />
<Route path="/account/home/user-profile" element={<AccountUserProfilePage />} /> <Route path="/account/home/user-profile" element={<AccountUserProfilePage />} />
<Route path="/groups/group-management" element={<ManageGroups />} />
<Route path="/members/kyc" element={<Kyc />} />
<Route path="/members/member-management" element={<ManageMembers />} />
<Route path="/access/access-type-management" element={<AccessType />} />
<Route path="/members/create-member-credential" element={<MemberCredential />} />
<Route path="/accounts/account-management" element={<ManageAccount />} />
<Route path="/accounts/currency-management" element={<ManageCurrency />} />
<Route path="/transfer-type/transfer-type-management" element={<TransferType />} />
<Route path="/notification/notification-management" element={<ManageNotification />} />
<Route path="/menu/menu-category" element={<MenuCategory />} />
<Route path="/menu/menu-management" element={<ManageMenu />} />
<Route path="/menu/welcome" element={<Welcome />} />
<Route path="/message/inbox" element={<Inbox />} />
<Route path="/webservice/webservice-management" element={<ManageWebServices />} />
<Route path="/settings/user-management/manage-user" element={<ManageUserPage />} /> <Route path="/settings/user-management/manage-user" element={<ManageUserPage />} />
<Route path="/settings/user-management/log-activity" element={<LogActivityPage />} /> <Route path="/settings/user-management/log-activity" element={<LogActivityPage />} />
<Route <Route

View File

@ -984,6 +984,13 @@
resolved "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz" resolved "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz"
integrity sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ== integrity sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==
"@mui/icons-material@^6.4.6":
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"
"@mui/material@^6.1.6": "@mui/material@^6.1.6":
version "6.1.6" version "6.1.6"
resolved "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz" resolved "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz"