diff --git a/env/env.cloud b/env/env.cloud index 6b72a4b..828fa71 100644 --- a/env/env.cloud +++ b/env/env.cloud @@ -1,3 +1,6 @@ VITE_APP_MODE=production VITE_BASE_API_URL=http://103.187.147.241:30050/api VITE_BASE_API_REPORT_URL=http://103.187.147.241:30050/api + +VITE_BASE_API_URL_LOCAL=https://api.office.weplayground.id/api +VITE_BASE_ACCESS_SETTING=Endy|dev \ No newline at end of file diff --git a/env/env.development b/env/env.development index e9c0373..1df931e 100644 --- a/env/env.development +++ b/env/env.development @@ -1,3 +1,6 @@ VITE_APP_MODE=production VITE_BASE_API_URL=https://api.sky.eigen.co.id/api VITE_BASE_API_REPORT_URL=https://api.sky.eigen.co.id/api + +VITE_BASE_API_URL_LOCAL=https://api.sky.eigen.co.id/api +VITE_BASE_ACCESS_SETTING=Endy|dev \ No newline at end of file diff --git a/env/env.production-offline b/env/env.production-offline index 2c6687b..f6a4172 100644 --- a/env/env.production-offline +++ b/env/env.production-offline @@ -1,3 +1,6 @@ VITE_APP_MODE=production VITE_BASE_API_URL=http://172.16.2.101:30050/api VITE_BASE_API_REPORT_URL=http://172.16.2.101:30050/api + +VITE_BASE_API_URL_LOCAL=http://172.16.2.101:30050/api +VITE_BASE_ACCESS_SETTING=Endy|dev \ No newline at end of file diff --git a/env/env.production-online b/env/env.production-online index 4dbed92..8d1c0b9 100644 --- a/env/env.production-online +++ b/env/env.production-online @@ -1,3 +1,6 @@ VITE_APP_MODE=production -VITE_BASE_API_URL=https://api.office.weplayground.id/api -VITE_BASE_API_REPORT_URL=https://api.office.weplayground.id/api +VITE_BASE_API_URL=http://103.187.147.241:30050/api +VITE_BASE_API_REPORT_URL=http://103.187.147.241:30050/api + +VITE_BASE_API_URL_LOCAL=http://103.187.147.241:30050/api +VITE_BASE_ACCESS_SETTING=Endy|dev \ No newline at end of file diff --git a/package.json b/package.json index 6922e35..f2b9457 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "preview": "vite preview" }, "dependencies": { - "antd": "^5.21.2", "ag-grid-community": "^31.3.2", "ag-grid-enterprise": "^31.3.2", "ag-grid-react": "^31.3.2", + "antd": "^5.21.2", "axios": "^1.7.7", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", diff --git a/src/apps/admin/index.tsx b/src/apps/admin/index.tsx index 56d2981..c67900b 100644 --- a/src/apps/admin/index.tsx +++ b/src/apps/admin/index.tsx @@ -1,277 +1,19 @@ -import axios from 'axios'; +import { lazy } from 'react'; import AdminLayout from './layout'; -import { API_URL, currencyFormatter } from '@pos/base'; -import { useEffect, useState } from 'react'; -import { Card, Col, DatePicker, notification, Row, Table } from 'antd'; -import dayjs from 'dayjs'; -import lodash from 'lodash'; -import { v4 } from 'uuid'; +import { Navigate, Route, Routes } from 'react-router-dom'; -export default function Admin() { - const [dataItem, setDataItem] = useState([]); - const [dataItemKeys, setDataItemKeys] = useState([]); - const [loadingDataItem, setLoadingDataItem] = useState(false); - const [dataItemTotalPax, setDataItemTotalPax] = useState(0); - const [dataItemTotalRevenue, setDataItemTotalRevenue] = useState(0); - - const [dataItemMaster, setDataItemMaster] = useState([]); - const [dataItemMasterKeys, setDataItemMasterKeys] = useState([]); - const [loadingDataItemMaster, setLoadingDataItemMaster] = useState(false); - const [dataItemMasterTotalPax, setDataItemMasterTotalPax] = useState(0); - const [dataItemMasterTotalRevenue, setDataItemMasterTotalRevenue] = useState(0); - - const [filterDate, setFilerDate] = useState(dayjs()); - - async function getDataItem(params: any) { - setLoadingDataItem(true); - await axios - .get(API_URL.REPORT_SUMMARY_INCOME_ITEM, { params: params }) - .then((resp) => { - const data = resp.data.data; - - const groupedData = lodash(data) - .groupBy('item_owner') // Group by item_owner - .map((items, owner) => ({ - // Map over each group to sum values and keep children - title: owner, - tr_item__qty: lodash.sumBy(items, (item) => Number(item.tr_item__qty)), // Convert to number - tr_item__total_net_price: lodash.sumBy(items, (item) => Number(item.tr_item__total_net_price)), // Convert to number - children: items.map((item) => { - return { ...item, title: item.tr_item__item_name }; - }), // Include the original data as children - })) - .value() - .map((item) => { - return { - key: v4(), - ...item, - }; - }); - - const totalPax = lodash.sumBy(data, (item: any) => Number(item.tr_item__qty)); - const totalRevenue = lodash.sumBy(data, (item: any) => Number(item.tr_item__total_net_price)); - setDataItemTotalPax(totalPax); - setDataItemTotalRevenue(totalRevenue); - - setDataItemKeys(groupedData.map((item) => item.key)); - setDataItem(groupedData); - }) - .catch((err) => { - notification.error({ message: err?.message }); - }) - .finally(() => { - setLoadingDataItem(false); - }); - } - - async function getDataItemMaster(params: any) { - setLoadingDataItemMaster(true); - await axios - .get(API_URL.REPORT_SUMMARY_INCOME_ITEM_MASTER, { params: params }) - .then((resp) => { - const data = resp.data.data; - const groupedData = lodash(data) - .groupBy('item_owner') // Group by item_owner - .map((items, owner) => ({ - // Map over each group to sum values and keep children - title: owner, - tr_item__qty: lodash.sumBy(items, (item) => Number(item.tr_item__qty)), // Convert to number - tr_item_bundling__total_net_price: lodash.sumBy(items, (item) => - Number(item.tr_item_bundling__total_net_price), - ), // Convert to number - children: items.map((item) => { - let title = ''; - if (item.tr_item_bundling__item_name) { - title = `${item.tr_item_bundling__item_name} / ${item.tr_item__item_name}`; - } else { - title = item.tr_item__item_name; - } - return { ...item, title: title }; - }), // Include the original data as children - })) - .value() - .map((item) => { - return { - key: v4(), - ...item, - }; - }); - - const totalPax = lodash.sumBy(data, (item: any) => Number(item.tr_item__qty)); - const totalRevenue = lodash.sumBy(data, (item: any) => Number(item.tr_item_bundling__total_net_price)); - setDataItemMasterTotalPax(totalPax); - setDataItemMasterTotalRevenue(totalRevenue); - - setDataItemMasterKeys(groupedData.map((item) => item.key)); - setDataItemMaster(groupedData); - }) - .catch((err) => { - notification.error({ message: err?.message }); - }) - .finally(() => { - setLoadingDataItemMaster(false); - }); - } - - function handleGetDate(date: any) { - getDataItem({ date }); - getDataItemMaster({ date }); - } - - useEffect(() => { - if (filterDate) { - handleGetDate(filterDate.format('DD-MM-YYYY')); - } - }, [filterDate]); +const ReportModule = lazy(() => import('./pages/report')); +const SettingModule = lazy(() => import('./pages/setting')); +export default function AppModule() { return ( - - - - - -
- - - - - -
{`Pendapatan Per Item ${filterDate.format('DD-MM-YYYY')}`}
- - -
{`Total revenue mungkin berbeda dengan pendapatan per item master disebabkan pengambilan harga kepada harga bundling.`}
- -
- } - > - - - -
-
TOTAL PAX
-
{dataItemTotalPax}
-
-
- - - -
-
TOTAL REVENUE
-
- {currencyFormatter({ value: dataItemTotalRevenue })} -
-
-
- -
-
- child.key} // Make sure each child row has a unique key - expandable={{ expandedRowKeys: dataItemKeys, showExpandColumn: false }} - rowClassName={(row) => (row.key ? 'row-group' : '')} - rowHoverable={false} - columns={[ - { key: 'title', dataIndex: 'title', title: 'TITLE', width: 170 }, - { key: 'tr_item__qty', dataIndex: 'tr_item__qty', title: 'PAX', width: 70 }, - { - key: 'tr_item__total_net_price', - dataIndex: 'tr_item__total_net_price', - title: 'REVENUE', - width: 120, - render: (value) => currencyFormatter({ value }), - }, - ]} - /> - - - - - -
{`Pendapatan Per Item Master ${filterDate.format('DD-MM-YYYY')}`}
- - -
{`Total revenue mungkin berbeda dengan pendapatan per item disebabkan harga item master mengambil harga jual standard item.`}
- - - } - > - - - -
-
TOTAL PAX
-
- {dataItemMasterTotalPax} -
-
-
- - - -
-
TOTAL REVENUE
-
- {currencyFormatter({ value: dataItemMasterTotalRevenue })} -
-
-
- - -
-
child.key} // Make sure each child row has a unique key - expandable={{ expandedRowKeys: dataItemMasterKeys, showExpandColumn: false }} - rowClassName={(row) => (row.key ? 'row-group' : '')} - rowHoverable={false} - columns={[ - { key: 'title', dataIndex: 'title', title: 'TITLE', width: 170 }, - - { key: 'tr_item__qty', dataIndex: 'tr_item__qty', title: 'PAX', width: 70 }, - { - key: 'tr_item_bundling__total_net_price', - dataIndex: 'tr_item_bundling__total_net_price', - title: 'REVENUE', - width: 120, - render: (value) => currencyFormatter({ value }), - }, - ]} - /> - - - + + } /> + } /> + } /> + } /> + ); } diff --git a/src/apps/admin/layout/components/local-data-configuration.tsx b/src/apps/admin/layout/components/local-data-configuration.tsx new file mode 100644 index 0000000..46280c8 --- /dev/null +++ b/src/apps/admin/layout/components/local-data-configuration.tsx @@ -0,0 +1,144 @@ +import axios from 'axios'; +import { useEffect, useState } from 'react'; +import { App, Button, Col, InputNumber, InputNumberProps, Modal, ModalProps, Row, Slider } from 'antd'; +import { API_URL, BASE_API_URL_LOCAL } from '@pos/base'; + +export default function LocalDataConfiguration(modalProps: ModalProps) { + const { modal, notification } = App.useApp(); + + const [configData, setConfigData] = useState(); + const [inputValue, setInputValue] = useState(0); + const [loadingGet, setLoadingGet] = useState(false); + const [loadingSave, setLoadingSave] = useState(false); + const onChange: InputNumberProps['onChange'] = (newValue) => { + setInputValue(newValue as number); + }; + + function makeColorText(value: number) { + if (value <= 30) { + return 'text-red-500'; + } else if (value > 30 && value <= 80) { + return 'text-yellow-500'; + } else if (value > 80 && value <= 100) { + return 'text-green-500'; + } + } + + async function handleSave(value: number) { + setLoadingSave(true); + await axios + .put(API_URL.EDIT_TRANSACTION_SETTING, { id: configData?.id, value }, { baseURL: BASE_API_URL_LOCAL }) + .then(() => { + notification.success({ + message: 'Sukses', + description: 'Konfigurasi berhasil disimpan', + }); + + if (modalProps.onCancel) modalProps.onCancel(null as any); + }) + .catch((err) => { + notification.error({ + message: 'Gagal', + description: err?.message ?? err?.response?.data?.message ?? 'Terjadi kesalahan saat menyimpan konfigurasi', + }); + }) + .finally(() => { + setLoadingSave(false); + }); + } + + async function handleGetData() { + setLoadingGet(true); + await axios + .get(API_URL.GET_TRANSACTION_SETTING, { baseURL: BASE_API_URL_LOCAL }) + .then((res) => { + const data = res.data.data; + const respValue = data?.value; + setInputValue(Number(respValue ?? '0')); + setConfigData(data); + }) + .catch((err) => { + notification.error({ + message: 'Gagal', + description: err?.message ?? err?.response?.data?.message ?? 'Terjadi kesalahan saat mengambil konfigurasi', + }); + }) + .finally(() => { + setLoadingGet(false); + }); + } + + useEffect(() => { + if (modalProps.open) handleGetData(); + }, [modalProps.open]); + return ( + + Batal + , + , + ]} + > +
{`Gunakan slider atau input number dibawah ini untuk menentukan persentase data yang ingin ditampilkan di aplikasi lokal Anda. Semakin tinggi persentasenya, semakin banyak data yang akan ditampilkan. Sesuaikan dengan kebutuhan untuk memastikan performa aplikasi tetap optimal.`}
+
+ +
+ + + + + + + + + ); +} diff --git a/src/apps/admin/layout/index.tsx b/src/apps/admin/layout/index.tsx index 1d4c1c5..04cd658 100644 --- a/src/apps/admin/layout/index.tsx +++ b/src/apps/admin/layout/index.tsx @@ -1,33 +1,58 @@ -import { Avatar, Flex, Image, Layout, Popconfirm, Tooltip } from 'antd'; +import { App, Avatar, Dropdown, Flex, Image, Layout } from 'antd'; import { ReactNode, useState } from 'react'; import { Content, Header } from 'antd/es/layout/layout'; -import { API_URL, getInitialName, handleLogout, UserDataState } from '@pos/base'; +import { ACCESS_SETTING, API_URL, getInitialName, handleLogout, UserDataState } from '@pos/base'; import { FaUser } from 'react-icons/fa'; import axios from 'axios'; import Logo from '../../../base/presentation/assets/images/we-logo.png'; import { useRecoilValue } from 'recoil'; +import LocalDataConfiguration from './components/local-data-configuration'; +import { FileTextOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons'; +import { useNavigate } from 'react-router-dom'; interface AdminLayoutProps { children: ReactNode; } export default function AdminLayout(props: AdminLayoutProps) { + const navigate = useNavigate(); + const { modal } = App.useApp(); + const { children } = props; - const [_loadingLogout, setLoadingLogout] = useState(false); const user = useRecoilValue(UserDataState); const initialName = getInitialName(user?.name ?? ''); + const [openModalConfig, setOpenModalConfig] = useState(false); + const onCancelModalConfig = () => setOpenModalConfig(false); + // const onOpenModalConfig = () => setOpenModalConfig(true); + async function handleClickLogout() { - setLoadingLogout(true); try { await axios({ url: `${API_URL.LOGOUT}`, method: 'delete' }); - setLoadingLogout(false); await handleLogout(); } catch (err: any) { - setLoadingLogout(false); + console.error(err); } } + function checkAllowAccessSetting() { + const username = user?.username; + const accessSetting = ACCESS_SETTING ?? ''; + + const allowList = accessSetting.split('|'); + if (allowList.includes(username)) return true; + return false; + } + + function gotoHome() { + navigate('/app'); + } + + function gotoSetting() { + // onOpenModalConfig(); + navigate('/app/setting'); + } + return (
- - {/*
WE POS
*/} +
- - handleClickLogout()} + + , + onClick: () => gotoHome(), + }, + { + type: 'divider', + key: 'divider_1', + }, + { + key: '2', + label: 'Setting', + icon: , + onClick: () => gotoSetting(), + }, + { + type: 'divider', + key: 'divider_2', + }, + { + key: '3', + label: 'Logout', + icon: , + onClick: () => { + modal.confirm({ + icon: null, + cancelText: 'Batal', + cancelButtonProps: { style: { width: 100 } }, + okButtonProps: { style: { width: 100 } }, + content:
Apakah anda yakin ingin keluar dari aplikasi?
, + onOk: () => handleClickLogout(), + }); + }, + }, + ] + .map((item) => { + const isAllowSetting = checkAllowAccessSetting(); + if (!isAllowSetting && ['1', '2', 'divider_1', 'divider_2'].includes(item.key)) { + return undefined; + } + return item; + }) + .filter(Boolean) as any, + }} > - - - {initialName ? ( -
{initialName?.toUpperCase()}
- ) : ( - - )} -
-
-
+ + {initialName ? ( +
{initialName?.toUpperCase()}
+ ) : ( + + )} +
+
+ {children}
); diff --git a/src/apps/admin/pages/components/default-value.tsx b/src/apps/admin/pages/components/default-value.tsx new file mode 100644 index 0000000..0f21404 --- /dev/null +++ b/src/apps/admin/pages/components/default-value.tsx @@ -0,0 +1,177 @@ +import dayjs from 'dayjs'; +import { Button, Card, Col, Flex, InputNumber, Modal, Row, Form } from 'antd'; +import { Fragment, useEffect, useState } from 'react'; +import axios from 'axios'; +import { notificationError } from '@pos/base'; +import { makeColorTextValue } from '../helpers'; + +export default function DefaultValue() { + const [loadingDefault, setLoadingDefault] = useState(false); + const [loadingActive, setLoadingActive] = useState(false); + + const [loadingSave, setLoadingSave] = useState(false); + const [defaultPercentage, setDefaultPercentage] = useState(100); + const [currentPercentage, setCurrentPercentage] = useState(100); + + const [form] = Form.useForm(); + const [openModal, setOpenModal] = useState(false); + + const onCloseModal = async () => { + await form.resetFields(); + setOpenModal(false); + }; + + const onOpenModal = async () => { + await form.setFieldsValue({ default_value: defaultPercentage }); + setOpenModal(true); + }; + + const handleSaveData = async () => { + try { + const payload = await form.validateFields(); + setLoadingSave(true); + await axios + .post('v1/data-scheduling-default', payload) + .then(async (resp) => { + const value = resp.data?.data?.default_value; + await form.setFieldsValue({ default_value: value }); + await handleGetDataActive(); + setDefaultPercentage(value); + setLoadingSave(false); + onCloseModal(); + }) + .catch((err) => { + const message = err.message; + if (message) notificationError(message); + setLoadingSave(false); + }); + } catch (error) { + console.error(error); + } + }; + + const handleGetDataActive = async () => { + setLoadingActive(true); + await axios + .get('v1/data-scheduling-active', { params: { date: dayjs().format('YYYY-MM-DD') } }) + .then((resp) => { + const value = resp.data?.data?.value; + form.setFieldsValue({ value: value }); + setCurrentPercentage(value); + }) + .catch((err) => { + const message = err.message; + if (message) notificationError(message); + }) + .finally(() => { + setLoadingActive(false); + }); + }; + + const handleGetDataDefault = async () => { + setLoadingDefault(true); + await axios + .get('v1/data-scheduling-default') + .then((resp) => { + const value = resp.data?.data?.default_value; + form.setFieldsValue({ default_value: value }); + setDefaultPercentage(value); + }) + .catch((err) => { + const message = err.message; + if (message) notificationError(message); + }) + .finally(() => { + setLoadingDefault(false); + }); + }; + + const handleGetData = async () => { + await Promise.all([handleGetDataDefault(), handleGetDataActive()]); + }; + + useEffect(() => { + handleGetData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + DEFAULT PERCENTAGE} + > +
+ + + + + +
{`%`}
+
+
+
+ {`*Value akan diterapkan otomatis jika tidak ada pengaturan khusus pada tanggal tersebut.`} +
+ +
+ +
+ + +
DEFAULT PERCENTAGE
+ +
{`${defaultPercentage >= 0 ? `${defaultPercentage} %` : '-'}`}
+ +
+
+ {`Value otomatis jika tak ada setelan khusus.`} +
+
+
+ + + + +
CURRENT PERCENTAGE
+ +
{`${currentPercentage >= 0 ? `${currentPercentage} %` : '-'}`}
+ +
+
+ {`Value yang di terapkan hari ini ${dayjs().format('DD-MM-YYYY')}.`} +
+
+
+ + + + ); +} diff --git a/src/apps/admin/pages/components/scheduling-data.tsx b/src/apps/admin/pages/components/scheduling-data.tsx new file mode 100644 index 0000000..eded63c --- /dev/null +++ b/src/apps/admin/pages/components/scheduling-data.tsx @@ -0,0 +1,430 @@ +import dayjs from 'dayjs'; +import axios from 'axios'; +import { Fragment, useEffect, useState } from 'react'; +import { + 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, PlusOutlined, UnlockOutlined } from '@ant-design/icons'; +import { makeColorTextValue } from '../helpers'; + +export default function SchedulingData() { + const [formModal] = Form.useForm(); + const { modal } = App.useApp(); + const [openModalForm, setOpenModalForm] = useState(false); + + const [loadingTable, setLoadingTable] = useState(false); + const [schedulingData, setSchedulingData] = useState([]); + const [schedulingMeta, setSchedulingMeta] = useState(); + + const [loadingForm, setLoadingForm] = useState(false); + + const handleGetData = async (page: number) => { + setLoadingTable(true); + await axios + .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) => { + const data = resp?.data?.data ?? []; + const meta = resp?.data?.meta; + setSchedulingMeta(meta); + setSchedulingData(data); + setLoadingTable(false); + }) + .catch(() => { + setLoadingTable(false); + }); + }; + + const handleActivate = async (id: string) => { + await axios + .patch(`v1/data-scheduling/${id}/active`) + .then(() => { + handleGetData(schedulingMeta?.currentPage); + }) + .catch((err: any) => { + notification.error({ + message: 'Gagal mengaktifkan data.', + description: + err?.message ?? err?.response?.data?.message ?? 'Terjadi kesalahan saat mengaktifkan konfigurasi', + }); + }); + }; + + const handleDeactivate = async (id: string) => { + await axios + .patch(`v1/data-scheduling/${id}/inactive`) + .then(() => { + handleGetData(schedulingMeta?.currentPage); + }) + .catch((err: any) => { + notification.error({ + message: 'Gagal menonaktifkan data.', + description: + err?.message ?? err?.response?.data?.message ?? 'Terjadi kesalahan saat menonaktifkan konfigurasi', + }); + }); + }; + + const handleDelete = async (id: string) => { + await axios + .delete(`v1/data-scheduling/${id}`) + .then(() => { + handleGetData(schedulingMeta?.currentPage); + }) + .catch((err: any) => { + notification.error({ + message: 'Gagal menghapus data.', + description: err?.message ?? err?.response?.data?.message ?? 'Terjadi kesalahan saat menghapus konfigurasi', + }); + }); + }; + + 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(() => { + handleGetData(1); + }, []); + + return ( +
+ +
+ + +
+ + + + + + + + + + + + {({ getFieldsValue }) => { + const values = getFieldsValue(); + const endDate = values?.schedule_date_to; + + return ( + + { + if (endDate) + return ( + (!date.isBefore(endDate) && !date.isSame(endDate)) || + date.isBefore(dayjs().subtract(1, 'day')) + ); + else return date.isBefore(dayjs().subtract(1, 'day')); + }} + /> + + ); + }} + + + + + {({ getFieldsValue }) => { + const values = getFieldsValue(); + const startDate = values?.schedule_date_from; + + return ( + + { + if (startDate) return !date.isAfter(startDate) && !date.isSame(startDate); + else return date.isBefore(dayjs().subtract(1, 'day')); + }} + /> + + ); + }} + + + + + +
+ + + + {schedulingData?.length > 0 && ( + + + + )} +
+ ( + + + + +
+ +
+
+ {capitalizeEachWord(item.status)} +
+
{item.name}
+
{`${dayjs(item?.schedule_date_from).format('DD MMM YYYY')} - ${dayjs(item?.schedule_date_to).format('DD MMM YYYY')}`}
+
+
+ + + +
{`${item.indexing_key} %`}
+
+ + + + + + + + +
+ + +
+ + +
{`Pendapatan Per Item ${filterDate.format('DD-MM-YYYY')}`}
+ + +
{`Total revenue mungkin berbeda dengan pendapatan per item master disebabkan pengambilan harga kepada harga bundling.`}
+ + + } + > + + + +
+
TOTAL PAX
+
{dataItemTotalPax}
+
+
+ + + +
+
TOTAL REVENUE
+
+ {currencyFormatter({ value: dataItemTotalRevenue })} +
+
+
+ + +
+
child.key} // Make sure each child row has a unique key + expandable={{ expandedRowKeys: dataItemKeys, showExpandColumn: false }} + rowClassName={(row) => (row.key ? 'row-group' : '')} + rowHoverable={false} + columns={[ + { key: 'title', dataIndex: 'title', title: 'TITLE', width: 170 }, + { key: 'tr_item__qty', dataIndex: 'tr_item__qty', title: 'PAX', width: 70 }, + { + key: 'tr_item__total_net_price', + dataIndex: 'tr_item__total_net_price', + title: 'REVENUE', + width: 120, + render: (value) => currencyFormatter({ value }), + }, + ]} + /> + + + + + +
{`Pendapatan Per Item Master ${filterDate.format('DD-MM-YYYY')}`}
+ + +
{`Total revenue mungkin berbeda dengan pendapatan per item disebabkan harga item master mengambil harga jual standard item.`}
+ + + } + > + + + +
+
TOTAL PAX
+
+ {dataItemMasterTotalPax} +
+
+
+ + + +
+
TOTAL REVENUE
+
+ {currencyFormatter({ value: dataItemMasterTotalRevenue })} +
+
+
+ + +
+
child.key} // Make sure each child row has a unique key + expandable={{ expandedRowKeys: dataItemMasterKeys, showExpandColumn: false }} + rowClassName={(row) => (row.key ? 'row-group' : '')} + rowHoverable={false} + columns={[ + { key: 'title', dataIndex: 'title', title: 'TITLE', width: 170 }, + + { key: 'tr_item__qty', dataIndex: 'tr_item__qty', title: 'PAX', width: 70 }, + { + key: 'tr_item_bundling__total_net_price', + dataIndex: 'tr_item_bundling__total_net_price', + title: 'REVENUE', + width: 120, + render: (value) => currencyFormatter({ value }), + }, + ]} + /> + + + + + ); +} diff --git a/src/apps/admin/pages/setting.tsx b/src/apps/admin/pages/setting.tsx new file mode 100644 index 0000000..f9eb924 --- /dev/null +++ b/src/apps/admin/pages/setting.tsx @@ -0,0 +1,36 @@ +import { ACCESS_SETTING, UserDataState } from '@pos/base'; +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useRecoilValue } from 'recoil'; + +import DefaultValue from './components/default-value'; +import SchedulingData from './components/scheduling-data'; +import { Flex } from 'antd'; + +export default function SettingModule() { + const user = useRecoilValue(UserDataState); + const navigate = useNavigate(); + + function checkAllowAccessSetting() { + const username = user?.username; + const accessSetting = ACCESS_SETTING ?? ''; + + const allowList = accessSetting.split('|'); + if (allowList.includes(username)) return true; + return false; + } + + useEffect(() => { + if (!checkAllowAccessSetting()) { + navigate('/app'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + ); +} diff --git a/src/apps/index.tsx b/src/apps/index.tsx index ac691a3..627aaa0 100644 --- a/src/apps/index.tsx +++ b/src/apps/index.tsx @@ -1,34 +1,36 @@ import { Suspense, lazy } from 'react'; import { RecoilRoot } from 'recoil'; -import { ConfigProvider, Flex, Spin } from 'antd'; +import { ConfigProvider, Flex, Spin, App as AntdApp } from 'antd'; import { DebugObserver, ForbiddenAccessPage, NotFoundPage } from '@pos/base'; import { Navigate, Route, Routes } from 'react-router-dom'; import { APP_THEME } from '@pos/base/presentation/assets/themes'; import { LoadingOutlined } from '@ant-design/icons'; const AuthApp = lazy(() => import('./auth')); -const PrivateApp = lazy(() => import('./admin')); +const AppModule = lazy(() => import('./admin/index')); export default function App() { return ( - - } /> - - } - > - - } /> - } /> - } /> - } /> - } /> - - + + + } /> + + } + > + + } /> + } /> + } /> + } /> + } /> + + + ); diff --git a/src/base/infrastructure/constants/api-url/index.ts b/src/base/infrastructure/constants/api-url/index.ts index d5855d7..774e89b 100644 --- a/src/base/infrastructure/constants/api-url/index.ts +++ b/src/base/infrastructure/constants/api-url/index.ts @@ -5,4 +5,7 @@ export const API_URL = { REPORT_SUMMARY_INCOME_ITEM: '/v1/report-summary/income-item', REPORT_SUMMARY_INCOME_ITEM_MASTER: '/v1/report-summary/income-item-master', + + EDIT_TRANSACTION_SETTING: '/v1/transaction-setting', + GET_TRANSACTION_SETTING: '/v1/transaction-setting/detail', }; diff --git a/src/base/infrastructure/constants/environment/index.ts b/src/base/infrastructure/constants/environment/index.ts index edadee5..50d770c 100644 --- a/src/base/infrastructure/constants/environment/index.ts +++ b/src/base/infrastructure/constants/environment/index.ts @@ -8,3 +8,6 @@ export const EMBED_DASHBOARD_ID = import.meta.env.VITE_EMBED_DASHBOARD_ID; export const DOWLOAD_POS_WINDOWS_URL = import.meta.env.VITE_DOWLOAD_POS_WINDOWS_URL; export const DOWLOAD_POS_LINUX_DEB_URL = import.meta.env.VITE_DOWLOAD_POS_LINUX_DEB_URL; export const DOWLOAD_POS_LINUX_SNAP_URL = import.meta.env.VITE_DOWLOAD_POS_LINUX_SNAP_URL; + +export const BASE_API_URL_LOCAL = import.meta.env.VITE_BASE_API_URL_LOCAL; +export const ACCESS_SETTING = import.meta.env.VITE_BASE_ACCESS_SETTING;