update sites crud done
This commit is contained in:
@ -29,6 +29,11 @@ const STATIC_MENU: MappedMenu[] = [
|
|||||||
path: '-',
|
path: '-',
|
||||||
children: [{ title: 'Dashboard', path: '/' }]
|
children: [{ title: 'Dashboard', path: '/' }]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Manage Sites',
|
||||||
|
path: '-',
|
||||||
|
children: [{ title: 'Manage Sites', path: '/sites/manage-sites' }]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Revenue',
|
title: 'Revenue',
|
||||||
path: '-',
|
path: '-',
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export default function ManageUserPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>TPAY | Manage User</title>
|
<title>REVENUE | Manage User</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<ManageUserContextProvider>
|
<ManageUserContextProvider>
|
||||||
<Container>
|
<Container>
|
||||||
|
|||||||
45
src/pages/sites/manage-sites/ManageSitesPage.tsx
Normal file
45
src/pages/sites/manage-sites/ManageSitesPage.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Container, DataGridInner } from '@/components';
|
||||||
|
import { EditDialog } from './blocks';
|
||||||
|
import { ManageSitesContextProvider } from './hooks';
|
||||||
|
import { AddDialog } from './blocks/AddDialog';
|
||||||
|
import { DeleteDialog } from './blocks/DeleteDialog';
|
||||||
|
import { Breadcrumbs, Link } from '@mui/material';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
|
export default function ManageSitesPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>REVENUE | Manage Sites</title>
|
||||||
|
</Helmet>
|
||||||
|
<ManageSitesContextProvider>
|
||||||
|
<Container>
|
||||||
|
<h1 className="text-xl font-medium leading-none text-gray-900 mb-3">Manage Sites</h1>
|
||||||
|
<Breadcrumbs sx={{ mb: 2 }}>
|
||||||
|
<Link underline="none" color="inherit" href="/">
|
||||||
|
<span className="text-sm hover:underline">Dashboard</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* <Link underline="none" color="inherit">
|
||||||
|
<span className="text-sm">Settings</span>
|
||||||
|
</Link> */}
|
||||||
|
|
||||||
|
{/* <Link underline="none" color="inherit">
|
||||||
|
<span className="text-sm">User Management</span>
|
||||||
|
</Link> */}
|
||||||
|
|
||||||
|
<Link underline="none" color="inherit">
|
||||||
|
<span className="text-sm">Manage Sites</span>
|
||||||
|
</Link>
|
||||||
|
</Breadcrumbs>
|
||||||
|
<div className="grid gap-5 lg:gap-7.5">
|
||||||
|
<DataGridInner />
|
||||||
|
</div>
|
||||||
|
<EditDialog />
|
||||||
|
<AddDialog />
|
||||||
|
<DeleteDialog />
|
||||||
|
</Container>
|
||||||
|
</ManageSitesContextProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
245
src/pages/sites/manage-sites/blocks/AddDialog.tsx
Normal file
245
src/pages/sites/manage-sites/blocks/AddDialog.tsx
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { useSitesContext } from '../hooks';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { apiConfig } from '@/config/api.config';
|
||||||
|
import { KeenIcon, useDataGrid } from '@/components';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useCallApi } from '@/hooks';
|
||||||
|
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
||||||
|
import {
|
||||||
|
initialStateCreateSites,
|
||||||
|
validateFormCreateSites
|
||||||
|
} from './Types';
|
||||||
|
|
||||||
|
const API_URL = apiConfig.service_master_data;
|
||||||
|
|
||||||
|
const AddDialog = () => {
|
||||||
|
const parentRef = useRef<any | null>(null);
|
||||||
|
const { showAddDialog, handleAddDialog } = useSitesContext();
|
||||||
|
const { reload } = useDataGrid();
|
||||||
|
const { PostData } = useCallApi();
|
||||||
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
|
const [formField, setFormField] = useState(initialStateCreateSites);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormField(initialStateCreateSites);
|
||||||
|
setErrors({});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* actions */
|
||||||
|
const doCreateSites = useCallback(
|
||||||
|
async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await PostData(`${API_URL}/site-points/create`, formField);
|
||||||
|
|
||||||
|
if (response?.status) {
|
||||||
|
handleAddDialog(false);
|
||||||
|
resetForm();
|
||||||
|
reload();
|
||||||
|
const createActivity = {
|
||||||
|
module: 'Manage Sites',
|
||||||
|
description: `Create New Sites => ${formField.location}`,
|
||||||
|
action: 'C'
|
||||||
|
};
|
||||||
|
|
||||||
|
doSaveLogActivity(createActivity);
|
||||||
|
toast.success('Success Create Site');
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Something went wrong');
|
||||||
|
console.log(err);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formField]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (isSubmitting) return;
|
||||||
|
|
||||||
|
if (!validateFormCreateSites(formField, setErrors, 'create')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
doCreateSites(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showAddDialog === false) {
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
}, [showAddDialog]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
|
||||||
|
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-10 overflow-hidden [&>button]:hidden">
|
||||||
|
<DialogHeader className="p-0 border-0">
|
||||||
|
<DialogTitle></DialogTitle>
|
||||||
|
<DialogDescription></DialogDescription>
|
||||||
|
<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">Site - Create</h1>
|
||||||
|
</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 p-5 pb-0" ref={parentRef}>
|
||||||
|
<form onSubmit={handleSubmit} className="grid gap-5">
|
||||||
|
{/* Cell */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Cell</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.cell ? 'border-red-500' : ''}`}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.cell}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
setFormField((prev) => ({ ...prev, cell: target.value }));
|
||||||
|
setErrors((prev) => ({ ...prev, cell: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.cell && <span className="text-red-500 text-xs mt-1">{errors.cell}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sector */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Sector</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.sector ? 'border-red-500' : ''}`}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.sector}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
setFormField((prev) => ({ ...prev, sector: target.value }));
|
||||||
|
setErrors((prev) => ({ ...prev, sector: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.sector && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.sector}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Site */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Site</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.site ? 'border-red-500' : ''}`}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.site}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
setFormField((prev) => ({ ...prev, site: target.value }));
|
||||||
|
setErrors((prev) => ({ ...prev, site: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.site && <span className="text-red-500 text-xs mt-1">{errors.site}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Latitude */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Latitude</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.lat ? 'border-red-500' : ''}`}
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.lat}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
const value = target.value === '' ? 0 : Number(target.value);
|
||||||
|
setFormField((prev) => ({ ...prev, lat: value }));
|
||||||
|
setErrors((prev) => ({ ...prev, lat: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.lat && <span className="text-red-500 text-xs mt-1">{errors.lat}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Longitude */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Longitude</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.lng ? 'border-red-500' : ''}`}
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.lng}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
const value = target.value === '' ? 0 : Number(target.value);
|
||||||
|
setFormField((prev) => ({ ...prev, lng: value }));
|
||||||
|
setErrors((prev) => ({ ...prev, lng: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.lng && <span className="text-red-500 text-xs mt-1">{errors.lng}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Location */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Location</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.location ? 'border-red-500' : ''}`}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.location}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
setFormField((prev) => ({ ...prev, location: target.value }));
|
||||||
|
setErrors((prev) => ({ ...prev, location: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.location && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.location}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<div className="flex justify-end pt-2.5">
|
||||||
|
<Button className="btn btn-primary" type="submit" disabled={isSubmitting}>
|
||||||
|
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogBody>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { AddDialog };
|
||||||
92
src/pages/sites/manage-sites/blocks/DeleteDialog.tsx
Normal file
92
src/pages/sites/manage-sites/blocks/DeleteDialog.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { useSitesContext } from '../hooks';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Alert, useDataGrid } from '@/components';
|
||||||
|
import { ChangeEvent, useCallback, useState } from 'react';
|
||||||
|
import { apiConfig } from '@/config/api.config';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useCallApi } from '@/hooks';
|
||||||
|
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
||||||
|
import { EnforceSwitch } from '@/components/switch';
|
||||||
|
|
||||||
|
const API_URL = apiConfig.service_master_data;
|
||||||
|
|
||||||
|
const DeleteDialog = () => {
|
||||||
|
const { showDeleteDialog, handleDeleteDialog, selectedSites } = useSitesContext();
|
||||||
|
const { reload } = useDataGrid();
|
||||||
|
const [enforce, setEnforce] = useState(false);
|
||||||
|
|
||||||
|
const { DeleteData } = useCallApi();
|
||||||
|
const [alert, setAlert] = useState({
|
||||||
|
show: false,
|
||||||
|
message: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
/* actions */
|
||||||
|
const doDeleteData = useCallback(async () => {
|
||||||
|
const response = await DeleteData(`${API_URL}/site-points/delete/${selectedSites?.id}/false`, {
|
||||||
|
id: selectedSites?.id
|
||||||
|
});
|
||||||
|
if (response?.status) {
|
||||||
|
setAlert((prev) => ({ ...prev, show: false, message: '' }));
|
||||||
|
handleDeleteDialog(false, null);
|
||||||
|
toast.success('Success Delete Sites');
|
||||||
|
reload();
|
||||||
|
const createActivity = {
|
||||||
|
module: 'Manage Sites',
|
||||||
|
description: `Delete Sites => ${selectedSites}`,
|
||||||
|
action: 'D'
|
||||||
|
};
|
||||||
|
|
||||||
|
doSaveLogActivity(createActivity);
|
||||||
|
} else {
|
||||||
|
setAlert((prev) => ({ ...prev, show: true, message: response?.message }));
|
||||||
|
}
|
||||||
|
}, [selectedSites, DeleteData, handleDeleteDialog, reload, enforce]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={showDeleteDialog} onOpenChange={(open) => handleDeleteDialog(open, null)}>
|
||||||
|
<DialogContent className="container-fixed max-w-md flex flex-col p-5 overflow-hidden [&>button]:hidden">
|
||||||
|
<DialogHeader className="p-0 border-0 block">
|
||||||
|
<DialogTitle></DialogTitle>
|
||||||
|
<DialogDescription></DialogDescription>
|
||||||
|
<Alert variant="warning">
|
||||||
|
<h3 className="text-lg">Are you sure?</h3>
|
||||||
|
<span className="text-sm">you will delete this data!</span>
|
||||||
|
{/* <div className="mt-2 flex items-center gap-x-2">
|
||||||
|
<label className="form-label max-w-56">Hard Delete</label>
|
||||||
|
<EnforceSwitch
|
||||||
|
enforce={enforce}
|
||||||
|
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setEnforce(e.target.checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
|
</Alert>
|
||||||
|
{alert.show && (
|
||||||
|
<Alert variant="danger">
|
||||||
|
<h3>{alert.message}</h3>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter className="flex justify-end items-center gap-4 mt-3">
|
||||||
|
<Button variant={'outline'} onClick={() => handleDeleteDialog(false, null)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant={'destructive'} onClick={() => doDeleteData()}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { DeleteDialog };
|
||||||
261
src/pages/sites/manage-sites/blocks/EditDialog.tsx
Normal file
261
src/pages/sites/manage-sites/blocks/EditDialog.tsx
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { useSitesContext } from '../hooks';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { apiConfig } from '@/config/api.config';
|
||||||
|
import { KeenIcon, useDataGrid } from '@/components';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useCallApi } from '@/hooks';
|
||||||
|
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
||||||
|
import {
|
||||||
|
initialStateCreateSites,
|
||||||
|
validateFormCreateSites
|
||||||
|
} from './Types';
|
||||||
|
|
||||||
|
const API_URL = apiConfig.service_master_data;
|
||||||
|
|
||||||
|
const EditDialog = () => {
|
||||||
|
const parentRef = useRef<any | null>(null);
|
||||||
|
const { showEditDialog, selectedSites, handleEditDialog } = useSitesContext();
|
||||||
|
const { reload } = useDataGrid();
|
||||||
|
const { PutData } = useCallApi();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
|
const [formField, setFormField] = useState(initialStateCreateSites);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormField(initialStateCreateSites);
|
||||||
|
setErrors({});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* actions */
|
||||||
|
const doUpdateSites = useCallback(
|
||||||
|
async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
if (!validateFormCreateSites(formField, setErrors, 'update')) {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await PutData(`${API_URL}/site-points/update/${selectedSites?.id}`, {
|
||||||
|
...formField
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.status) {
|
||||||
|
resetForm();
|
||||||
|
handleEditDialog(false, null);
|
||||||
|
toast.success('Success Update Site');
|
||||||
|
reload();
|
||||||
|
const createActivity = {
|
||||||
|
module: 'Manage Sites',
|
||||||
|
description: `Edit Site => ${formField.location}`,
|
||||||
|
action: 'U'
|
||||||
|
};
|
||||||
|
|
||||||
|
doSaveLogActivity(createActivity);
|
||||||
|
} else {
|
||||||
|
toast.error(response?.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('Something went wrong, please try again.');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selectedSites, formField]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showEditDialog === false) {
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
}, [showEditDialog]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedSites) {
|
||||||
|
setFormField((prev) => ({
|
||||||
|
...prev,
|
||||||
|
cell: selectedSites.cell ?? '',
|
||||||
|
sector: selectedSites.sector ?? '',
|
||||||
|
site: selectedSites.site ?? '',
|
||||||
|
lat: selectedSites.lat ?? 0,
|
||||||
|
lng: selectedSites.lng ?? 0,
|
||||||
|
location: selectedSites.location ?? ''
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [selectedSites]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open, null)}>
|
||||||
|
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
|
||||||
|
<DialogHeader className="p-0 border-0">
|
||||||
|
<DialogTitle></DialogTitle>
|
||||||
|
<DialogDescription></DialogDescription>
|
||||||
|
<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">Site - Update</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={() => handleEditDialog(false, null)}
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
<form onSubmit={doUpdateSites}>
|
||||||
|
<div className="card-body grid gap-5 p-0">
|
||||||
|
{/* Cell */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Cell</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.cell ? 'border-red-500' : ''}`}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.cell}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
setFormField((prev) => ({ ...prev, cell: target.value }));
|
||||||
|
setErrors((prev) => ({ ...prev, cell: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.cell && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.cell}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sector */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Sector</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.sector ? 'border-red-500' : ''}`}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.sector}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
setFormField((prev) => ({ ...prev, sector: target.value }));
|
||||||
|
setErrors((prev) => ({ ...prev, sector: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.sector && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.sector}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Site */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Site</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.site ? 'border-red-500' : ''}`}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.site}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
setFormField((prev) => ({ ...prev, site: target.value }));
|
||||||
|
setErrors((prev) => ({ ...prev, site: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.site && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.site}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Latitude */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Latitude</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.lat ? 'border-red-500' : ''}`}
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.lat}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
const value = target.value === '' ? 0 : Number(target.value);
|
||||||
|
setFormField((prev) => ({ ...prev, lat: value }));
|
||||||
|
setErrors((prev) => ({ ...prev, lat: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.lat && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.lat}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Longitude */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Longitude</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.lng ? 'border-red-500' : ''}`}
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.lng}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
const value = target.value === '' ? 0 : Number(target.value);
|
||||||
|
setFormField((prev) => ({ ...prev, lng: value }));
|
||||||
|
setErrors((prev) => ({ ...prev, lng: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.lng && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.lng}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Location */}
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">Location</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
|
<Input
|
||||||
|
className={`input ${errors.location ? 'border-red-500' : ''}`}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={formField.location}
|
||||||
|
onChange={({ target }) => {
|
||||||
|
setFormField((prev) => ({ ...prev, location: target.value }));
|
||||||
|
setErrors((prev) => ({ ...prev, location: '' }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.location && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.location}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end pt-2.5">
|
||||||
|
<Button className="btn btn-primary" type="submit" disabled={isSubmitting}>
|
||||||
|
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DialogBody>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { EditDialog };
|
||||||
76
src/pages/sites/manage-sites/blocks/ListToolBar.tsx
Normal file
76
src/pages/sites/manage-sites/blocks/ListToolBar.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components';
|
||||||
|
import { useSitesContext } from '../hooks';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const ListToolBar = () => {
|
||||||
|
const { table, reload } = useDataGrid();
|
||||||
|
const { handleAddDialog } = useSitesContext();
|
||||||
|
const [searchValue, setSearchValue] = useState<string>(
|
||||||
|
(table.getColumn('cell')?.getFilterValue() as string) ?? ''
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
table.getColumn('cell')?.setFilterValue(searchValue);
|
||||||
|
table.setPageIndex(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
table.getColumn('cell')?.setFilterValue(searchValue);
|
||||||
|
table.setPageIndex(0);
|
||||||
|
}, 200);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchValue, table]);
|
||||||
|
|
||||||
|
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 overflow-hidden">
|
||||||
|
<KeenIcon icon="magnifier" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Cell"
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(event) => setSearchValue(event.target.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{/* <DefaultTooltip title={'Search'} placement={'top'}>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="h-7.5 disabled:bg-gray-400"
|
||||||
|
onClick={handleSearch}
|
||||||
|
>
|
||||||
|
<KeenIcon icon="magnifier" />
|
||||||
|
</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 };
|
||||||
69
src/pages/sites/manage-sites/blocks/Types.ts
Normal file
69
src/pages/sites/manage-sites/blocks/Types.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
export interface CreateSitesrParams {
|
||||||
|
cell: string;
|
||||||
|
sector: string;
|
||||||
|
site: string;
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
location: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialStateCreateSites: {
|
||||||
|
cell: string;
|
||||||
|
sector: string;
|
||||||
|
site: string;
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
location: string;
|
||||||
|
} = {
|
||||||
|
cell: '',
|
||||||
|
sector: '',
|
||||||
|
site: '',
|
||||||
|
lat: 0,
|
||||||
|
lng: 0,
|
||||||
|
location: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateFormCreateSites = (
|
||||||
|
formField: typeof initialStateCreateSites,
|
||||||
|
setErrors: React.Dispatch<React.SetStateAction<Record<string, string>>>,
|
||||||
|
mode: 'create' | 'update' = 'create'
|
||||||
|
) => {
|
||||||
|
const requiredFields =
|
||||||
|
mode === 'create'
|
||||||
|
? [
|
||||||
|
{ key: 'cell', label: 'Cell' },
|
||||||
|
{ key: 'sector', label: 'Sector' },
|
||||||
|
{ key: 'site', label: 'Site' },
|
||||||
|
{ key: 'lat', label: 'Lat' },
|
||||||
|
{ key: 'lng', label: 'Longitude' },
|
||||||
|
{ key: 'lat', label: 'Latitude' }
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{ key: 'cell', label: 'Cell' },
|
||||||
|
{ key: 'sector', label: 'Sector' },
|
||||||
|
{ key: 'site', label: 'Site' },
|
||||||
|
{ key: 'lat', label: 'Lat' },
|
||||||
|
{ key: 'lng', label: 'Longitude' },
|
||||||
|
{ key: 'lat', label: 'Latitude' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const newErrors: Record<string, string> = {};
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
requiredFields.forEach(({ key, label }) => {
|
||||||
|
if (
|
||||||
|
formField[key as keyof typeof formField] === '' ||
|
||||||
|
formField[key as keyof typeof formField] === null ||
|
||||||
|
formField[key as keyof typeof formField] === undefined
|
||||||
|
) {
|
||||||
|
newErrors[key] = `${label} is required`;
|
||||||
|
toast.error(`${label} is required`);
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setErrors(newErrors);
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
2
src/pages/sites/manage-sites/blocks/index.ts
Normal file
2
src/pages/sites/manage-sites/blocks/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './ListToolBar';
|
||||||
|
export * from './EditDialog';
|
||||||
246
src/pages/sites/manage-sites/hooks/ManageSitesContext.tsx
Normal file
246
src/pages/sites/manage-sites/hooks/ManageSitesContext.tsx
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
import React, { createContext, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { apiConfig } from '@/config/api.config';
|
||||||
|
import { Toaster } from '@/components/ui/sonner';
|
||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components';
|
||||||
|
import { EnforceSwitch } from '@/components/switch';
|
||||||
|
import { ListToolBar } from '../blocks';
|
||||||
|
import { useCallApi } from '@/hooks';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import { MoreHorizontal, MoreVertical, Pencil, QrCode, Trash } from 'lucide-react';
|
||||||
|
import GenerateQr from '@/pages/account/home/user-profile/blocks/GenerateQr';
|
||||||
|
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
|
||||||
|
|
||||||
|
// Objek data lengkap satu site (dipakai untuk Edit, Delete, dan QR)
|
||||||
|
interface SelectedSites {
|
||||||
|
id: string;
|
||||||
|
cell: string;
|
||||||
|
sector: string;
|
||||||
|
site: string;
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
location: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextProps {
|
||||||
|
showSearchDialog: boolean;
|
||||||
|
handleSearchDialog: (show: boolean) => void;
|
||||||
|
showEditDialog: boolean;
|
||||||
|
handleEditDialog: (show: boolean, selected_sites: SelectedSites | null) => void;
|
||||||
|
showAddDialog: boolean;
|
||||||
|
handleAddDialog: (show: boolean) => void;
|
||||||
|
showDeleteDialog: boolean;
|
||||||
|
handleDeleteDialog: (show: boolean, selected_sites: SelectedSites | null) => void;
|
||||||
|
selectedSites: SelectedSites | null;
|
||||||
|
showQr: boolean;
|
||||||
|
setShowQr: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialProps: ContextProps = {
|
||||||
|
showSearchDialog: false,
|
||||||
|
handleSearchDialog: (show: boolean) => { },
|
||||||
|
showEditDialog: false,
|
||||||
|
handleEditDialog: () => { },
|
||||||
|
showAddDialog: false,
|
||||||
|
handleAddDialog: () => { },
|
||||||
|
showDeleteDialog: false,
|
||||||
|
handleDeleteDialog: () => { },
|
||||||
|
selectedSites: null,
|
||||||
|
showQr: false,
|
||||||
|
setShowQr: () => { }
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManageSitesContext = createContext<ContextProps>(initialProps);
|
||||||
|
|
||||||
|
const API_URL = apiConfig.service_master_data;
|
||||||
|
|
||||||
|
const ManageSitesContextProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
/* state */
|
||||||
|
const [showEditDialog, setShowEditDialog] = useState(false);
|
||||||
|
const [showSearchDialog, setShowSearchDialog] = useState(false);
|
||||||
|
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||||
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
|
const [showQr, setShowQr] = useState(false);
|
||||||
|
const [selectedSites, setselectedSites] = useState<SelectedSites | null>(null);
|
||||||
|
const { GetData } = useCallApi();
|
||||||
|
|
||||||
|
/* action */
|
||||||
|
const handleSearchDialog = useCallback((show: boolean) => {
|
||||||
|
setShowSearchDialog(show);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleEditDialog = useCallback((show: boolean, selected_sites: SelectedSites | null) => {
|
||||||
|
setselectedSites(show ? selected_sites : null);
|
||||||
|
setShowEditDialog(show);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAddDialog = useCallback((show: boolean) => {
|
||||||
|
setShowAddDialog(show);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDeleteDialog = useCallback((show: boolean, selected_sites: SelectedSites | null) => {
|
||||||
|
setselectedSites(show ? selected_sites : null);
|
||||||
|
setShowDeleteDialog(show);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/* Data Grid Options */
|
||||||
|
const columns = useMemo<ColumnDef<any>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'cell',
|
||||||
|
id: 'cell',
|
||||||
|
header: ({ column }) => <DataGridColumnHeader title="cell" column={column} />,
|
||||||
|
enableSorting: true,
|
||||||
|
enableHiding: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'sector',
|
||||||
|
id: 'sector',
|
||||||
|
header: ({ column }) => <DataGridColumnHeader title="sector" column={column} />,
|
||||||
|
enableSorting: true,
|
||||||
|
enableHiding: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'site',
|
||||||
|
id: 'site',
|
||||||
|
header: ({ column }) => <DataGridColumnHeader title="site" column={column} />,
|
||||||
|
enableSorting: true,
|
||||||
|
enableHiding: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'lat',
|
||||||
|
id: 'lat',
|
||||||
|
header: ({ column }) => <DataGridColumnHeader title="lat" column={column} />,
|
||||||
|
enableSorting: true,
|
||||||
|
enableHiding: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'lng',
|
||||||
|
id: 'lng',
|
||||||
|
header: ({ column }) => <DataGridColumnHeader title="lng" column={column} />,
|
||||||
|
enableSorting: true,
|
||||||
|
enableHiding: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'location',
|
||||||
|
id: 'location',
|
||||||
|
header: ({ column }) => <DataGridColumnHeader title="location" column={column} />,
|
||||||
|
enableSorting: true,
|
||||||
|
enableHiding: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
header: ({ column }) => <DataGridColumnHeader title="Action" column={column} />,
|
||||||
|
cell: (data: any) => {
|
||||||
|
const row = data.row.original;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<button className="btn btn-sm btn-icon btn-light">
|
||||||
|
<MoreHorizontal className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-48">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setShowQr(true);
|
||||||
|
setselectedSites(row);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => handleEditDialog(true, row)}>
|
||||||
|
<Pencil className="w-4 h-4 mr-2" />
|
||||||
|
Edit
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => handleDeleteDialog(true, row)}>
|
||||||
|
<Trash className="w-4 h-4 mr-2 text-red-500" />
|
||||||
|
<span className="text-red-500">Delete</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
headerClassName: 'w-[100px]',
|
||||||
|
cellClassName: 'text-center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[handleEditDialog, handleDeleteDialog]
|
||||||
|
);
|
||||||
|
|
||||||
|
const doGetListData = async (page: number, limit: number, sorting: any, filter: any) => {
|
||||||
|
const USER_TABLE_COLUMNS = ['cell', 'sector', 'site', 'lat', 'long', 'location'];
|
||||||
|
|
||||||
|
// ➕ Tambahkan prefix ke field dari tabel Users
|
||||||
|
const mappedSorting = sorting.map((sort: any) => ({
|
||||||
|
...sort,
|
||||||
|
id: USER_TABLE_COLUMNS.includes(sort.id) ? `${sort.id}` : sort.id
|
||||||
|
}));
|
||||||
|
|
||||||
|
const orderField = mappedSorting[0]?.id ?? 'location';
|
||||||
|
const orderDirection = mappedSorting[0]?.desc === false ? 'DESC' : 'ASC';
|
||||||
|
|
||||||
|
filter =
|
||||||
|
filter.length == 0
|
||||||
|
? {}
|
||||||
|
: { 'cell': { like: `%${filter[0].value?.toLowerCase()}%` } };
|
||||||
|
const response = await GetData(`${API_URL}/site-points/list`, {
|
||||||
|
limit: limit,
|
||||||
|
page: page + 1,
|
||||||
|
with_deleted: false,
|
||||||
|
order_field: orderField,
|
||||||
|
order_direction: orderDirection,
|
||||||
|
filter: JSON.stringify(filter)
|
||||||
|
});
|
||||||
|
// console.log('response api:', response);
|
||||||
|
|
||||||
|
return { data: response?.data.list, totalCount: response?.data.total_count };
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ManageSitesContext.Provider
|
||||||
|
value={{
|
||||||
|
showSearchDialog,
|
||||||
|
handleSearchDialog,
|
||||||
|
showEditDialog,
|
||||||
|
handleEditDialog,
|
||||||
|
selectedSites,
|
||||||
|
showAddDialog,
|
||||||
|
handleAddDialog,
|
||||||
|
showDeleteDialog,
|
||||||
|
handleDeleteDialog,
|
||||||
|
showQr,
|
||||||
|
setShowQr
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DataGridProvider
|
||||||
|
columns={columns}
|
||||||
|
pagination={{ size: 10 }}
|
||||||
|
toolbar={<ListToolBar />}
|
||||||
|
layout={{ card: true }}
|
||||||
|
sorting={[{ id: 'created_at', desc: false }]}
|
||||||
|
serverSide={true}
|
||||||
|
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
|
||||||
|
doGetListData(pageIndex, pageSize, sorting, columnFilters)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DataGridProvider>
|
||||||
|
</ManageSitesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ManageSitesContextProvider, ManageSitesContext };
|
||||||
|
export type { SelectedSites };
|
||||||
2
src/pages/sites/manage-sites/hooks/index.ts
Normal file
2
src/pages/sites/manage-sites/hooks/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './ManageSitesContext';
|
||||||
|
export * from './useManageSitesContext';
|
||||||
12
src/pages/sites/manage-sites/hooks/useManageSitesContext.tsx
Normal file
12
src/pages/sites/manage-sites/hooks/useManageSitesContext.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { ManageSitesContext } from './ManageSitesContext';
|
||||||
|
|
||||||
|
const useSitesContext = () => {
|
||||||
|
const context = useContext(ManageSitesContext);
|
||||||
|
|
||||||
|
if (!context) throw new Error('useSitesContext must be used within AuthProvider');
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useSitesContext };
|
||||||
1
src/pages/sites/manage-sites/index.ts
Normal file
1
src/pages/sites/manage-sites/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './ManageSitesPage';
|
||||||
@ -8,6 +8,8 @@ import { ErrorsRouting } from '@/errors';
|
|||||||
|
|
||||||
import DashboardHomePage from '@/pages/dashboards/home/DashboardHomePage';
|
import DashboardHomePage from '@/pages/dashboards/home/DashboardHomePage';
|
||||||
import ManageUserPage from '@/pages/settings/user/manage-user/ManageUserPage';
|
import ManageUserPage from '@/pages/settings/user/manage-user/ManageUserPage';
|
||||||
|
import ManageSitesPage from '@/pages/sites/manage-sites/ManageSitesPage';
|
||||||
|
|
||||||
import Transaction from '@/pages/transaction/history-transaction/Transaction';
|
import Transaction from '@/pages/transaction/history-transaction/Transaction';
|
||||||
import ApprovalTransaction from '@/pages/transaction/approval-transaction/ApprovalTransaction';
|
import ApprovalTransaction from '@/pages/transaction/approval-transaction/ApprovalTransaction';
|
||||||
import TransactionTopup from '@/pages/transaction/topup/TransactionTopup';
|
import TransactionTopup from '@/pages/transaction/topup/TransactionTopup';
|
||||||
@ -66,6 +68,8 @@ const AppRoutingSetup = (): ReactElement => {
|
|||||||
<Route path="/coming-soon" element={<ComingSoonPage />} />
|
<Route path="/coming-soon" element={<ComingSoonPage />} />
|
||||||
<Route path="/settings/user-management/manage-user" element={<ManageUserPage />} />
|
<Route path="/settings/user-management/manage-user" element={<ManageUserPage />} />
|
||||||
|
|
||||||
|
<Route path="/sites/manage-sites" element={<ManageSitesPage />} />
|
||||||
|
|
||||||
{/* <Route path="/master-data" element={<MasterData />} />
|
{/* <Route path="/master-data" element={<MasterData />} />
|
||||||
<Route path="/master-data/municipios" element={<Municipios />} />
|
<Route path="/master-data/municipios" element={<Municipios />} />
|
||||||
<Route path="/master-data/postoadms" element={<PostoAdmsMaster />} />
|
<Route path="/master-data/postoadms" element={<PostoAdmsMaster />} />
|
||||||
|
|||||||
Reference in New Issue
Block a user