feat: integration CRUD

pull/1/head
Firman Ramdhani 2025-07-07 14:57:45 +07:00
parent 9cd4d7f1a0
commit e150e9e416
1 changed files with 348 additions and 116 deletions

View File

@ -1,27 +1,58 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import axios from 'axios'; import axios from 'axios';
import { App, Button, Card, Col, Flex, List, notification, Pagination, Row, Tag, Tooltip } from 'antd'; import { Fragment, useEffect, useState } from 'react';
import { useEffect, useState } from 'react'; import {
import { makeColorStatus, STATUS_DATA } from '@pos/base'; App,
Badge,
Button,
Card,
Col,
DatePicker,
Divider,
Flex,
Form,
Input,
InputNumber,
List,
Modal,
notification,
Pagination,
Row,
Tag,
Tooltip,
} from 'antd';
import { capitalizeEachWord, makeColorStatus, STATUS_DATA } from '@pos/base';
import { DeleteOutlined, EditOutlined, LockOutlined, UnlockOutlined } from '@ant-design/icons'; import { DeleteOutlined, EditOutlined, LockOutlined, PlusOutlined, UnlockOutlined } from '@ant-design/icons';
import { makeColorTextValue } from '../helpers'; import { makeColorTextValue } from '../helpers';
export default function SchedulingData() { export default function SchedulingData() {
const [formModal] = Form.useForm();
const { modal } = App.useApp(); const { modal } = App.useApp();
const [openModalForm, setOpenModalForm] = useState(false);
const [loadingTable, setLoadingTable] = useState(false); const [loadingTable, setLoadingTable] = useState(false);
const [schedulingData, setSchedulingData] = useState<any[]>([]); const [schedulingData, setSchedulingData] = useState<any[]>([]);
const [meta, setMeta] = useState<any>(); const [schedulingMeta, setSchedulingMeta] = useState<any>();
const [loadingForm, setLoadingForm] = useState(false);
const handleGetData = async (page: number) => { const handleGetData = async (page: number) => {
setLoadingTable(true); setLoadingTable(true);
await axios await axios
.get('v1/data-scheduling', { params: { page: page, limit: 10 } }) .get('v1/data-scheduling', {
params: {
page: page,
limit: 10,
order_type: 'ASC',
order_by: 'schedule_date_from',
schedule_date_from: dayjs().format('YYYY-MM-DD'),
},
})
.then((resp: any) => { .then((resp: any) => {
const data = resp?.data?.data ?? []; const data = resp?.data?.data ?? [];
const meta = resp?.data?.meta; const meta = resp?.data?.meta;
setMeta(meta); setSchedulingMeta(meta);
setSchedulingData(data); setSchedulingData(data);
setLoadingTable(false); setLoadingTable(false);
}) })
@ -34,7 +65,7 @@ export default function SchedulingData() {
await axios await axios
.patch(`v1/data-scheduling/${id}/active`) .patch(`v1/data-scheduling/${id}/active`)
.then(() => { .then(() => {
handleGetData(meta?.currentPage); handleGetData(schedulingMeta?.currentPage);
}) })
.catch((err: any) => { .catch((err: any) => {
notification.error({ notification.error({
@ -49,7 +80,7 @@ export default function SchedulingData() {
await axios await axios
.patch(`v1/data-scheduling/${id}/inactive`) .patch(`v1/data-scheduling/${id}/inactive`)
.then(() => { .then(() => {
handleGetData(meta?.currentPage); handleGetData(schedulingMeta?.currentPage);
}) })
.catch((err: any) => { .catch((err: any) => {
notification.error({ notification.error({
@ -64,7 +95,7 @@ export default function SchedulingData() {
await axios await axios
.delete(`v1/data-scheduling/${id}`) .delete(`v1/data-scheduling/${id}`)
.then(() => { .then(() => {
handleGetData(meta?.currentPage); handleGetData(schedulingMeta?.currentPage);
}) })
.catch((err: any) => { .catch((err: any) => {
notification.error({ notification.error({
@ -74,125 +105,326 @@ export default function SchedulingData() {
}); });
}; };
const handleClickUpdate = async (item: any) => {
await formModal.setFieldsValue({
id: item.id,
name: item.name,
indexing_key: item.indexing_key,
schedule_date_from: dayjs(item.schedule_date_from),
schedule_date_to: dayjs(item.schedule_date_from),
});
setOpenModalForm(true);
};
const handleClickCreate = async () => {
await formModal.resetFields();
setOpenModalForm(true);
};
const handleCloseModal = async () => {
await formModal.resetFields();
setOpenModalForm(false);
};
const handleSubmitModal = async () => {
const formValues = await formModal.validateFields();
const dataID = formValues.id;
const payload = {
name: formValues.name,
indexing_key: formValues.indexing_key,
schedule_date_from: formValues.schedule_date_from && dayjs(formValues.schedule_date_from).format('YYYY-MM-DD'),
schedule_date_to: formValues.schedule_date_from && dayjs(formValues.schedule_date_from).format('YYYY-MM-DD'),
};
if (dataID) handleEdit(dataID, payload);
else handleCreate(payload);
};
const handleCreate = async (payload: any) => {
setLoadingForm(true);
await axios
.post(`v1/data-scheduling`, payload)
.then(async () => {
await handleGetData(1);
await handleCloseModal();
})
.catch((err: any) => {
notification.error({
message: 'Gagal menyimpan data.',
description:
err?.message ?? err?.response?.data?.message ?? 'Terjadi kesalahan saat mengaktifkan konfigurasi',
});
})
.finally(() => {
setLoadingForm(false);
});
};
const handleEdit = async (id: string, payload: any) => {
setLoadingForm(true);
await axios
.put(`v1/data-scheduling/${id}`, payload)
.then(async () => {
await handleGetData(schedulingMeta?.currentPage);
await handleCloseModal();
})
.catch((err: any) => {
notification.error({
message: 'Gagal menyimpan data.',
description:
err?.message ?? err?.response?.data?.message ?? 'Terjadi kesalahan saat mengaktifkan konfigurasi',
});
})
.finally(() => {
setLoadingForm(false);
});
};
useEffect(() => { useEffect(() => {
handleGetData(1); handleGetData(1);
}, []); }, []);
return ( return (
<div> <div>
<Button>Tambah Jadwal</Button> <Modal
<div style={{ marginBottom: 10 }}></div> open={openModalForm}
<List onCancel={handleCloseModal}
bordered={!schedulingData || schedulingData?.length <= 0} cancelButtonProps={{ disabled: loadingForm }}
style={{ maxHeight: 500, overflow: 'auto' }} okText="Simpan"
dataSource={schedulingData} okButtonProps={{ loading: loadingForm }}
loading={loadingTable} onOk={handleSubmitModal}
renderItem={(item) => ( title="FORM KONFIGURASI PENJADWALAN"
<List.Item key={item.id} style={{ borderBlockEnd: 'none', padding: 5 }}> >
<Card style={{ width: '100%' }}> <Form form={formModal} layout="vertical">
<Flex justify="space-between" style={{ width: '100%' }}> <Form.Item name={['id']} noStyle></Form.Item>
<Row className="w-full" gutter={[4, 4]}> <Row gutter={[12, 1]}>
<Col xs={18} sm={12} span={12}> <Col xs={24} sm={12} span={12}>
<Flex align="center" className="h-full" gap={4}> <Form.Item name={['name']} label="Label" rules={[{ required: true, message: 'Label harus diisi!' }]}>
<div> <Input placeholder="Input label" size="large" />
<div className="text-[#00000066]">{item.name}</div> </Form.Item>
<div className="font-normal mt-1">{`${dayjs(item?.schedule_date_from).format('DD MMM YYYY')} - ${dayjs(item?.schedule_date_to).format('DD MMM YYYY')}`}</div> </Col>
</div> <Col xs={24} sm={12} span={12}>
</Flex> <Form.Item
</Col> name={['indexing_key']}
<Col xs={18} sm={12} span={12}> label="Total Data"
<Flex align="center" className="h-full" gap={8}> rules={[
<div { required: true, message: 'Total data harus diisi!' },
className={`text-2xl font-bold ${makeColorTextValue(item.indexing_key)}`} { type: 'number', max: 100, message: 'Total data maksimal 100%!' },
>{`${item.indexing_key} %`}</div> {
<Tag color={makeColorStatus(item.status)}>{item.status}</Tag> validator(_, value) {
</Flex> if (value <= 0) return Promise.reject(new Error('Total data harus lebih dari 0!'));
</Col> return Promise.resolve();
</Row> },
<Flex align="center" gap={4}> },
<Tooltip title="Edit" trigger={'hover'} placement="bottom"> ]}
<Button icon={<EditOutlined />} /> >
</Tooltip> <InputNumber style={{ width: '100%' }} addonAfter="%" placeholder="Input value" size="large" />
</Form.Item>
</Col>
<Col xs={24} sm={12} span={12}>
<Form.Item shouldUpdate noStyle>
{({ getFieldsValue }) => {
const values = getFieldsValue();
const endDate = values?.schedule_date_to;
{item.status === STATUS_DATA.ACTIVE && ( return (
<Tooltip title="Inactive" trigger={'hover'} placement="bottom"> <Form.Item
<Button label="Start Date"
icon={<LockOutlined />} name={'schedule_date_from'}
onClick={() => { rules={[{ required: true, message: 'Start date harus diisi!' }]}
modal.confirm({ >
icon: null, <DatePicker
cancelText: 'Batal', showNow={false}
title: 'Nonaktifkan Konfigurasi Penjadwalan?', style={{ width: '100%' }}
cancelButtonProps: { style: { width: 100 } }, size="large"
okButtonProps: { style: { width: 100 } }, disabledDate={(date) => {
content: ( if (endDate)
<div> return (
<div className="font-semibold italic">{`Perhatian: `}</div> (!date.isBefore(endDate) && !date.isSame(endDate)) ||
<div className="italic">{`Konfigurasi yang dinonaktifkan tidak akan diikutsertakan dalam proses penjadwalan hingga Anda mengaktifkannya kembali. Ini berguna untuk menjeda proses penjadwalan tertentu tanpa menghapus konfigurasi.`}</div> date.isBefore(dayjs().subtract(1, 'day'))
</div> );
), else return date.isBefore(dayjs().subtract(1, 'day'));
onOk: () => handleDeactivate(item.id),
});
}} }}
/> />
</Tooltip> </Form.Item>
)} );
}}
</Form.Item>
</Col>
<Col xs={24} sm={12} span={12}>
<Form.Item shouldUpdate noStyle>
{({ getFieldsValue }) => {
const values = getFieldsValue();
const startDate = values?.schedule_date_from;
{item.status === STATUS_DATA.INACTIVE && ( return (
<Tooltip title="Active" trigger={'hover'} placement="bottom"> <Form.Item
<Button label="End Date"
icon={<UnlockOutlined />} name={'schedule_date_to'}
onClick={() => { rules={[{ required: true, message: 'End date harus diisi!' }]}
modal.confirm({ >
icon: null, <DatePicker
cancelText: 'Batal', showNow={false}
title: 'Aktifkan Konfigurasi Penjadwalan?', style={{ width: '100%' }}
cancelButtonProps: { style: { width: 100 } }, size="large"
okButtonProps: { style: { width: 100 } }, disabledDate={(date) => {
content: ( if (startDate) return !date.isAfter(startDate) && !date.isSame(startDate);
<div> else return date.isBefore(dayjs().subtract(1, 'day'));
<div className="font-semibold italic">{`Perhatian: `}</div>
<div className="italic">{`Dengan mengaktifkannya, konfigurasi ini akan kembali diperhitungkan dan digunakan dalam proses penjadwalan selanjutnya, sesuai dengan kriteria yang telah ditetapkan.`}</div>
</div>
),
onOk: () => handleActivate(item.id),
});
}} }}
/> />
</Tooltip> </Form.Item>
)} );
}}
<Tooltip title="Delete" trigger={'hover'} placement="bottom"> </Form.Item>
<Button </Col>
icon={<DeleteOutlined />} </Row>
onClick={() => { </Form>
modal.confirm({ </Modal>
icon: null, <div>
cancelText: 'Batal', <Flex justify="end">
title: 'Hapus Konfigurasi Penjadwalan?', <Button type="primary" onClick={handleClickCreate} icon={<PlusOutlined />}>
cancelButtonProps: { style: { width: 100 } }, Tambah Jadwal
okButtonProps: { style: { width: 100 } }, </Button>
content: ( </Flex>
<div> {schedulingData?.length > 0 && (
<div className="font-semibold italic">{`Perhatian: `}</div> <Fragment>
<div className="italic">{`Tindakan ini bersifat permanen. Konfigurasi yang terhapus tidak akan lagi disertakan dalam perhitungan atau proses penjadwalan di masa mendatang dan tidak dapat dipulihkan.`}</div> <Divider style={{ margin: 10 }} />
</div> </Fragment>
),
onOk: () => handleDelete(item.id),
});
}}
/>
</Tooltip>
</Flex>
</Flex>
</Card>
</List.Item>
)} )}
/> <div style={{ marginBottom: 10 }}></div>
<div style={{ marginBottom: 10 }}></div> <List
<Flex justify="end"> bordered={!schedulingData || schedulingData?.length <= 0}
<Pagination current={meta?.currentPage} total={meta?.totalPages} /> style={{ maxHeight: 500, overflow: 'auto' }}
</Flex> dataSource={schedulingData}
loading={loadingTable}
renderItem={(item) => (
<List.Item key={item.id} style={{ borderBlockEnd: 'none', padding: 5 }}>
<Card className="w-full">
<Flex className="w-full" justify="space-between">
<Row className="w-full" gutter={[4, 8]}>
<Col xs={18} sm={12} span={12}>
<Flex align="center" className="h-full" gap={4}>
<div>
<div className="text-xs mb-2" style={{ color: makeColorStatus(item.status) }}>
{capitalizeEachWord(item.status)}
</div>
<div className="text-[#00000099]">{item.name}</div>
<div className="text-[#00000099]">{`${dayjs(item?.schedule_date_from).format('DD MMM YYYY')} - ${dayjs(item?.schedule_date_to).format('DD MMM YYYY')}`}</div>
</div>
</Flex>
</Col>
<Col xs={18} sm={12} span={12}>
<Flex align="center" className="h-full" gap={8}>
<div
className={`text-xl font-bold ${makeColorTextValue(item.indexing_key)}`}
>{`${item.indexing_key} %`}</div>
</Flex>
</Col>
</Row>
<Flex align="center" gap={4}>
<Tooltip title="Edit" trigger={'hover'} placement="bottom">
<Button icon={<EditOutlined />} onClick={() => handleClickUpdate(item)} />
</Tooltip>
{item.status === STATUS_DATA.ACTIVE && (
<Tooltip title="Inactive" trigger={'hover'} placement="bottom">
<Button
icon={<LockOutlined />}
onClick={() => {
modal.confirm({
icon: null,
cancelText: 'Batal',
title: 'Nonaktifkan Konfigurasi Penjadwalan?',
cancelButtonProps: { style: { width: 100 } },
okButtonProps: { style: { width: 100 } },
content: (
<div>
<div className="font-semibold italic">{`Perhatian: `}</div>
<div className="italic">{`Konfigurasi yang dinonaktifkan tidak akan diikutsertakan dalam proses penjadwalan hingga Anda mengaktifkannya kembali. Ini berguna untuk menjeda proses penjadwalan tertentu tanpa menghapus konfigurasi.`}</div>
</div>
),
onOk: () => handleDeactivate(item.id),
});
}}
/>
</Tooltip>
)}
{item.status === STATUS_DATA.INACTIVE && (
<Tooltip title="Active" trigger={'hover'} placement="bottom">
<Button
icon={<UnlockOutlined />}
onClick={() => {
modal.confirm({
icon: null,
cancelText: 'Batal',
title: 'Aktifkan Konfigurasi Penjadwalan?',
cancelButtonProps: { style: { width: 100 } },
okButtonProps: { style: { width: 100 } },
content: (
<div>
<div className="font-semibold italic">{`Perhatian: `}</div>
<div className="italic">{`Dengan mengaktifkannya, konfigurasi ini akan kembali diperhitungkan dan digunakan dalam proses penjadwalan selanjutnya, sesuai dengan kriteria yang telah ditetapkan.`}</div>
</div>
),
onOk: () => handleActivate(item.id),
});
}}
/>
</Tooltip>
)}
<Tooltip title="Delete" trigger={'hover'} placement="bottom">
<Button
icon={<DeleteOutlined />}
onClick={() => {
modal.confirm({
icon: null,
cancelText: 'Batal',
title: 'Hapus Konfigurasi Penjadwalan?',
cancelButtonProps: { style: { width: 100 } },
okButtonProps: { style: { width: 100 } },
content: (
<div>
<div className="font-semibold italic">{`Perhatian: `}</div>
<div className="italic">{`Tindakan ini bersifat permanen. Konfigurasi yang terhapus tidak akan lagi disertakan dalam perhitungan atau proses penjadwalan di masa mendatang dan tidak dapat dipulihkan.`}</div>
</div>
),
onOk: () => handleDelete(item.id),
});
}}
/>
</Tooltip>
</Flex>
</Flex>
</Card>
</List.Item>
)}
/>
<div style={{ marginBottom: 10 }}></div>
{schedulingData?.length > 0 && (
<Fragment>
<Divider style={{}} />
<Flex justify="end" className="mt-2">
<Pagination
current={schedulingMeta?.currentPage}
total={schedulingMeta?.totalItems}
pageSize={10}
onChange={async (page) => {
await handleGetData(page);
}}
/>
</Flex>
</Fragment>
)}
<div className="mt-2">
<div className="italic font-semibold">Informasi Penting: </div>
<div className="italic text-[#00000066]">
{`Daftar konfigurasi penjadwalan diurutkan otomatis berdasarkan Waktu Mulai. Konfigurasi baru akan muncul sesuai urutan tanggalnya, dan jadwal yang sudah terlewat tidak akan ditampilkan.`}
</div>
</div>
</div>
</div> </div>
); );
} }