fix group menu
This commit is contained in:
@ -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
26
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
25
src/components/confirm.tsx
Normal file
25
src/components/confirm.tsx
Normal 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;
|
||||||
156
src/components/ui/DataTable.tsx
Normal file
156
src/components/ui/DataTable.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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 };
|
||||||
|
|||||||
@ -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'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
21
src/pages/access/access-type/AccessType.tsx
Normal file
21
src/pages/access/access-type/AccessType.tsx
Normal 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;
|
||||||
141
src/pages/access/access-type/blocks/AddDialog.tsx
Normal file
141
src/pages/access/access-type/blocks/AddDialog.tsx
Normal 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;
|
||||||
49
src/pages/access/access-type/blocks/ListToolBar.tsx
Normal file
49
src/pages/access/access-type/blocks/ListToolBar.tsx
Normal 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;
|
||||||
162
src/pages/access/access-type/hooks/ManageAccessTypeContext.tsx
Normal file
162
src/pages/access/access-type/hooks/ManageAccessTypeContext.tsx
Normal 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 };
|
||||||
@ -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 };
|
||||||
21
src/pages/access/member-credentials/MemberCredentials.tsx
Normal file
21
src/pages/access/member-credentials/MemberCredentials.tsx
Normal 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;
|
||||||
@ -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;
|
||||||
1
src/pages/access/member-credentials/hooks/index.ts
Normal file
1
src/pages/access/member-credentials/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './useMemberCredentials';
|
||||||
@ -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 };
|
||||||
|
};
|
||||||
71
src/pages/account/manage-account/Columns.tsx
Normal file
71
src/pages/account/manage-account/Columns.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
83
src/pages/account/manage-account/ManageAccount.tsx
Normal file
83
src/pages/account/manage-account/ManageAccount.tsx
Normal 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;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import { apiConfig } from '@/config/api.config';
|
||||||
|
|
||||||
|
const API_URL = apiConfig.service_dashboard;
|
||||||
18
src/pages/account/manage-currency/ManageCurrency.tsx
Normal file
18
src/pages/account/manage-currency/ManageCurrency.tsx
Normal 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;
|
||||||
240
src/pages/account/manage-currency/blocks/AddDialog.tsx
Normal file
240
src/pages/account/manage-currency/blocks/AddDialog.tsx
Normal 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;
|
||||||
49
src/pages/account/manage-currency/blocks/ListToolBar.tsx
Normal file
49
src/pages/account/manage-currency/blocks/ListToolBar.tsx
Normal 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;
|
||||||
@ -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 };
|
||||||
@ -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 };
|
||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ const Chart = ({
|
|||||||
plotOptions: {
|
plotOptions: {
|
||||||
bar: {
|
bar: {
|
||||||
horizontal: false,
|
horizontal: false,
|
||||||
columnWidth: '50%'
|
columnWidth: '30%'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dataLabels: {
|
dataLabels: {
|
||||||
|
|||||||
56
src/pages/dashboards/home/staticChart.tsx
Normal file
56
src/pages/dashboards/home/staticChart.tsx
Normal 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']
|
||||||
|
}
|
||||||
|
};
|
||||||
37
src/pages/groups/Column.tsx
Normal file
37
src/pages/groups/Column.tsx
Normal 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'
|
||||||
|
// }
|
||||||
|
];
|
||||||
249
src/pages/groups/ManageGroups.tsx
Normal file
249
src/pages/groups/ManageGroups.tsx
Normal 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;
|
||||||
11
src/pages/master/MasterData.tsx
Normal file
11
src/pages/master/MasterData.tsx
Normal 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;
|
||||||
11
src/pages/master/aldeias/AldeiasMaster.tsx
Normal file
11
src/pages/master/aldeias/AldeiasMaster.tsx
Normal 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;
|
||||||
21
src/pages/master/municipios/Municipios.tsx
Normal file
21
src/pages/master/municipios/Municipios.tsx
Normal 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;
|
||||||
111
src/pages/master/municipios/blocks/AddDialog.tsx
Normal file
111
src/pages/master/municipios/blocks/AddDialog.tsx
Normal 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;
|
||||||
62
src/pages/master/municipios/blocks/ListToolbar.tsx
Normal file
62
src/pages/master/municipios/blocks/ListToolbar.tsx
Normal 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;
|
||||||
177
src/pages/master/municipios/blocks/SearchDialog.tsx
Normal file
177
src/pages/master/municipios/blocks/SearchDialog.tsx
Normal 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;
|
||||||
250
src/pages/master/municipios/hooks/ManageMunicipiosContext.tsx
Normal file
250
src/pages/master/municipios/hooks/ManageMunicipiosContext.tsx
Normal 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 };
|
||||||
@ -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 };
|
||||||
17
src/pages/master/postoadms/PostoAdmsMaster.tsx
Normal file
17
src/pages/master/postoadms/PostoAdmsMaster.tsx
Normal 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;
|
||||||
63
src/pages/master/postoadms/blocks/ListToolbar.tsx
Normal file
63
src/pages/master/postoadms/blocks/ListToolbar.tsx
Normal 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;
|
||||||
178
src/pages/master/postoadms/hooks/ManagePostoAdmsContext.tsx
Normal file
178
src/pages/master/postoadms/hooks/ManagePostoAdmsContext.tsx
Normal 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 };
|
||||||
@ -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 };
|
||||||
11
src/pages/master/sucos/SucosMaster.tsx
Normal file
11
src/pages/master/sucos/SucosMaster.tsx
Normal 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;
|
||||||
42
src/pages/members/kyc/Kyc.tsx
Normal file
42
src/pages/members/kyc/Kyc.tsx
Normal 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;
|
||||||
14
src/pages/members/kyc/blocks/AddDialog.tsx
Normal file
14
src/pages/members/kyc/blocks/AddDialog.tsx
Normal 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;
|
||||||
48
src/pages/members/kyc/blocks/ListToolBar.tsx
Normal file
48
src/pages/members/kyc/blocks/ListToolBar.tsx
Normal 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 };
|
||||||
145
src/pages/members/kyc/hooks/ManageKycContext.tsx
Normal file
145
src/pages/members/kyc/hooks/ManageKycContext.tsx
Normal 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 };
|
||||||
2
src/pages/members/kyc/hooks/index.ts
Normal file
2
src/pages/members/kyc/hooks/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './ManageKycContext';
|
||||||
|
export * from './useKycContext';
|
||||||
12
src/pages/members/kyc/hooks/useKycContext.tsx
Normal file
12
src/pages/members/kyc/hooks/useKycContext.tsx
Normal 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 };
|
||||||
119
src/pages/members/manage-members/Columns.tsx
Normal file
119
src/pages/members/manage-members/Columns.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
42
src/pages/members/manage-members/ManageMembers.tsx
Normal file
42
src/pages/members/manage-members/ManageMembers.tsx
Normal 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;
|
||||||
11
src/pages/menu/manage-menu/ManageMenu.tsx
Normal file
11
src/pages/menu/manage-menu/ManageMenu.tsx
Normal 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;
|
||||||
11
src/pages/menu/menu-category/MenuCategory.tsx
Normal file
11
src/pages/menu/menu-category/MenuCategory.tsx
Normal 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;
|
||||||
11
src/pages/menu/welcome/Welcome.tsx
Normal file
11
src/pages/menu/welcome/Welcome.tsx
Normal 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;
|
||||||
11
src/pages/message/Inbox.tsx
Normal file
11
src/pages/message/Inbox.tsx
Normal 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;
|
||||||
18
src/pages/notification/ManageNotification.tsx
Normal file
18
src/pages/notification/ManageNotification.tsx
Normal 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;
|
||||||
147
src/pages/notification/blocks/AddDialog.tsx
Normal file
147
src/pages/notification/blocks/AddDialog.tsx
Normal 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;
|
||||||
57
src/pages/notification/blocks/ListToolbar.tsx
Normal file
57
src/pages/notification/blocks/ListToolbar.tsx
Normal 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 };
|
||||||
142
src/pages/notification/hooks/ManageNotificationContext.tsx
Normal file
142
src/pages/notification/hooks/ManageNotificationContext.tsx
Normal 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 };
|
||||||
@ -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 };
|
||||||
@ -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}>
|
||||||
|
|||||||
21
src/pages/transfer/TransferType.tsx
Normal file
21
src/pages/transfer/TransferType.tsx
Normal 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;
|
||||||
294
src/pages/transfer/blocks/AddDialog.tsx
Normal file
294
src/pages/transfer/blocks/AddDialog.tsx
Normal 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;
|
||||||
49
src/pages/transfer/blocks/ListToolBar.tsx
Normal file
49
src/pages/transfer/blocks/ListToolBar.tsx
Normal 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;
|
||||||
178
src/pages/transfer/hooks/ManageTransferTypeContext.tsx
Normal file
178
src/pages/transfer/hooks/ManageTransferTypeContext.tsx
Normal 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 };
|
||||||
12
src/pages/transfer/hooks/useManageTransferTypeContext.tsx
Normal file
12
src/pages/transfer/hooks/useManageTransferTypeContext.tsx
Normal 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 };
|
||||||
11
src/pages/webservice/ManageWebServices.tsx
Normal file
11
src/pages/webservice/ManageWebServices.tsx
Normal 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;
|
||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user