feat: setup setting page

main-cloud
Firman Ramdhani 2025-07-03 17:48:54 +07:00
parent 29ff09d3aa
commit 4a08abe0b5
10 changed files with 356 additions and 283 deletions

3
env/env.cloud vendored
View File

@ -2,4 +2,5 @@ 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_API_URL_LOCAL=https://api.office.weplayground.id/api
VITE_BASE_ACCESS_SETTING=Endy|dev

1
env/env.development vendored
View File

@ -3,3 +3,4 @@ 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

View File

@ -2,4 +2,5 @@ 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_API_URL_LOCAL=http://172.16.2.101:30050/api
VITE_BASE_ACCESS_SETTING=Endy|dev

View File

@ -2,4 +2,5 @@ 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=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

View File

@ -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<any[]>([]);
const [dataItemKeys, setDataItemKeys] = useState<any[]>([]);
const [loadingDataItem, setLoadingDataItem] = useState<boolean>(false);
const [dataItemTotalPax, setDataItemTotalPax] = useState<number>(0);
const [dataItemTotalRevenue, setDataItemTotalRevenue] = useState<number>(0);
const [dataItemMaster, setDataItemMaster] = useState<any[]>([]);
const [dataItemMasterKeys, setDataItemMasterKeys] = useState<any[]>([]);
const [loadingDataItemMaster, setLoadingDataItemMaster] = useState<boolean>(false);
const [dataItemMasterTotalPax, setDataItemMasterTotalPax] = useState<number>(0);
const [dataItemMasterTotalRevenue, setDataItemMasterTotalRevenue] = useState<number>(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 (
<AdminLayout>
<Row>
<Col xl={8} lg={8} md={12} span={24}>
<DatePicker
size="large"
popupStyle={{ fontSize: 16 }}
allowClear={false}
value={filterDate}
style={{ width: '100%' }}
format={'DD-MM-YYYY'}
onChange={setFilerDate}
/>
</Col>
</Row>
<div style={{ marginBottom: 20 }}></div>
<Row gutter={[16, 16]}>
<Col xl={12} lg={12} span={24}>
<Card
title={
<Row style={{ paddingTop: 10, paddingBottom: 10 }}>
<Col span={24}>
<div
style={{ fontSize: 16, fontWeight: 600 }}
>{`Pendapatan Per Item ${filterDate.format('DD-MM-YYYY')}`}</div>
</Col>
<Col xl={20} lg={20} span={24}>
<div
style={{ fontWeight: 400, fontSize: 12, color: 'grey', textWrap: 'wrap' }}
>{`Total revenue mungkin berbeda dengan pendapatan per item master disebabkan pengambilan harga kepada harga bundling.`}</div>
</Col>
</Row>
}
>
<Row gutter={[8, 8]}>
<Col span={12}>
<Card styles={{ body: { padding: '6px 12px 6px 12px' } }}>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.4)' }}>TOTAL PAX</div>
<div style={{ fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.6)' }}>{dataItemTotalPax}</div>
</div>
</Card>
</Col>
<Col span={12}>
<Card styles={{ body: { padding: '6px 12px 6px 12px' } }}>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.4)' }}>TOTAL REVENUE</div>
<div style={{ fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.6)' }}>
{currencyFormatter({ value: dataItemTotalRevenue })}
</div>
</div>
</Card>
</Col>
</Row>
<div style={{ marginBottom: 10 }}></div>
<Table
bordered
size="small"
dataSource={dataItem}
pagination={false}
loading={loadingDataItem}
scroll={{ x: 'max-width', y: 350 }}
rowKey={(child) => 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 }),
},
]}
/>
</Card>
</Col>
<Col xl={12} lg={12} span={24}>
<Card
title={
<Row style={{ paddingTop: 10, paddingBottom: 10 }}>
<Col span={24}>
<div
style={{ fontSize: 16, fontWeight: 600 }}
>{`Pendapatan Per Item Master ${filterDate.format('DD-MM-YYYY')}`}</div>
</Col>
<Col xl={20} lg={20} span={24}>
<div
style={{ fontWeight: 400, fontSize: 12, color: 'grey', textWrap: 'wrap' }}
>{`Total revenue mungkin berbeda dengan pendapatan per item disebabkan harga item master mengambil harga jual standard item.`}</div>
</Col>
</Row>
}
>
<Row gutter={[8, 8]}>
<Col span={12}>
<Card styles={{ body: { padding: '6px 12px 6px 12px' } }}>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.4)' }}>TOTAL PAX</div>
<div style={{ fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.6)' }}>
{dataItemMasterTotalPax}
</div>
</div>
</Card>
</Col>
<Col span={12}>
<Card styles={{ body: { padding: '6px 12px 6px 12px' } }}>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.4)' }}>TOTAL REVENUE</div>
<div style={{ fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.6)' }}>
{currencyFormatter({ value: dataItemMasterTotalRevenue })}
</div>
</div>
</Card>
</Col>
</Row>
<div style={{ marginBottom: 10 }}></div>
<Table
bordered
size="small"
dataSource={dataItemMaster}
pagination={false}
loading={loadingDataItemMaster}
scroll={{ x: 'max-width', y: 350 }}
rowKey={(child) => 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 }),
},
]}
/>
</Card>
</Col>
</Row>
<Routes>
<Route path="/report" element={<ReportModule />} />
<Route path="/setting" element={<SettingModule />} />
<Route path="/" element={<Navigate to="/app/report" />} />
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
</Routes>
</AdminLayout>
);
}

View File

@ -1,19 +1,21 @@
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 { LogoutOutlined, SettingOutlined } from '@ant-design/icons';
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;
@ -22,7 +24,7 @@ export default function AdminLayout(props: AdminLayoutProps) {
const [openModalConfig, setOpenModalConfig] = useState(false);
const onCancelModalConfig = () => setOpenModalConfig(false);
const onOpenModalConfig = () => setOpenModalConfig(true);
// const onOpenModalConfig = () => setOpenModalConfig(true);
async function handleClickLogout() {
try {
@ -35,11 +37,22 @@ export default function AdminLayout(props: AdminLayoutProps) {
function checkAllowAccessSetting() {
const username = user?.username;
const allowList = ['Endy', 'superadmin'];
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 (
<Layout>
<Header
@ -56,7 +69,7 @@ export default function AdminLayout(props: AdminLayoutProps) {
>
<Flex style={{ width: '100%' }} align="center">
<Flex>
<Image src={Logo} width={50} preview={false} />
<Image src={Logo} width={50} preview={false} onClick={gotoHome} style={{ cursor: 'pointer' }} />
</Flex>
<Flex style={{ marginLeft: 'auto' }} gap={15} align="center">
<Dropdown
@ -66,15 +79,26 @@ export default function AdminLayout(props: AdminLayoutProps) {
items: [
{
key: '1',
label: 'Settings',
icon: <SettingOutlined />,
onClick: () => onOpenModalConfig(),
label: 'Report',
icon: <FileTextOutlined />,
onClick: () => gotoHome(),
},
{
type: 'divider',
key: 'divider_1',
},
{
key: '2',
label: 'Settings',
icon: <SettingOutlined />,
onClick: () => gotoSetting(),
},
{
type: 'divider',
key: 'divider_2',
},
{
key: '3',
label: 'Logout',
icon: <LogoutOutlined />,
onClick: () => {
@ -91,7 +115,7 @@ export default function AdminLayout(props: AdminLayoutProps) {
]
.map((item) => {
const isAllowSetting = checkAllowAccessSetting();
if (!isAllowSetting && (item.key === '1' || item.type === 'divider')) {
if (!isAllowSetting && ['1', '2', 'divider_1', 'divider_2'].includes(item.key)) {
return undefined;
}
return item;

View File

@ -0,0 +1,276 @@
import axios from 'axios';
import { API_URL, currencyFormatter } from '@pos/base';
import { Fragment, 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';
export default function ReportModule() {
const [dataItem, setDataItem] = useState<any[]>([]);
const [dataItemKeys, setDataItemKeys] = useState<any[]>([]);
const [loadingDataItem, setLoadingDataItem] = useState<boolean>(false);
const [dataItemTotalPax, setDataItemTotalPax] = useState<number>(0);
const [dataItemTotalRevenue, setDataItemTotalRevenue] = useState<number>(0);
const [dataItemMaster, setDataItemMaster] = useState<any[]>([]);
const [dataItemMasterKeys, setDataItemMasterKeys] = useState<any[]>([]);
const [loadingDataItemMaster, setLoadingDataItemMaster] = useState<boolean>(false);
const [dataItemMasterTotalPax, setDataItemMasterTotalPax] = useState<number>(0);
const [dataItemMasterTotalRevenue, setDataItemMasterTotalRevenue] = useState<number>(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]);
return (
<Fragment>
<Row>
<Col xl={8} lg={8} md={12} span={24}>
<DatePicker
size="large"
popupStyle={{ fontSize: 16 }}
allowClear={false}
value={filterDate}
style={{ width: '100%' }}
format={'DD-MM-YYYY'}
onChange={setFilerDate}
/>
</Col>
</Row>
<div style={{ marginBottom: 20 }}></div>
<Row gutter={[16, 16]}>
<Col xl={12} lg={12} span={24}>
<Card
title={
<Row style={{ paddingTop: 10, paddingBottom: 10 }}>
<Col span={24}>
<div
style={{ fontSize: 16, fontWeight: 600 }}
>{`Pendapatan Per Item ${filterDate.format('DD-MM-YYYY')}`}</div>
</Col>
<Col xl={20} lg={20} span={24}>
<div
style={{ fontWeight: 400, fontSize: 12, color: 'grey', textWrap: 'wrap' }}
>{`Total revenue mungkin berbeda dengan pendapatan per item master disebabkan pengambilan harga kepada harga bundling.`}</div>
</Col>
</Row>
}
>
<Row gutter={[8, 8]}>
<Col span={12}>
<Card styles={{ body: { padding: '6px 12px 6px 12px' } }}>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.4)' }}>TOTAL PAX</div>
<div style={{ fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.6)' }}>{dataItemTotalPax}</div>
</div>
</Card>
</Col>
<Col span={12}>
<Card styles={{ body: { padding: '6px 12px 6px 12px' } }}>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.4)' }}>TOTAL REVENUE</div>
<div style={{ fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.6)' }}>
{currencyFormatter({ value: dataItemTotalRevenue })}
</div>
</div>
</Card>
</Col>
</Row>
<div style={{ marginBottom: 10 }}></div>
<Table
bordered
size="small"
dataSource={dataItem}
pagination={false}
loading={loadingDataItem}
scroll={{ x: 'max-width', y: 350 }}
rowKey={(child) => 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 }),
},
]}
/>
</Card>
</Col>
<Col xl={12} lg={12} span={24}>
<Card
title={
<Row style={{ paddingTop: 10, paddingBottom: 10 }}>
<Col span={24}>
<div
style={{ fontSize: 16, fontWeight: 600 }}
>{`Pendapatan Per Item Master ${filterDate.format('DD-MM-YYYY')}`}</div>
</Col>
<Col xl={20} lg={20} span={24}>
<div
style={{ fontWeight: 400, fontSize: 12, color: 'grey', textWrap: 'wrap' }}
>{`Total revenue mungkin berbeda dengan pendapatan per item disebabkan harga item master mengambil harga jual standard item.`}</div>
</Col>
</Row>
}
>
<Row gutter={[8, 8]}>
<Col span={12}>
<Card styles={{ body: { padding: '6px 12px 6px 12px' } }}>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.4)' }}>TOTAL PAX</div>
<div style={{ fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.6)' }}>
{dataItemMasterTotalPax}
</div>
</div>
</Card>
</Col>
<Col span={12}>
<Card styles={{ body: { padding: '6px 12px 6px 12px' } }}>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.4)' }}>TOTAL REVENUE</div>
<div style={{ fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.6)' }}>
{currencyFormatter({ value: dataItemMasterTotalRevenue })}
</div>
</div>
</Card>
</Col>
</Row>
<div style={{ marginBottom: 10 }}></div>
<Table
bordered
size="small"
dataSource={dataItemMaster}
pagination={false}
loading={loadingDataItemMaster}
scroll={{ x: 'max-width', y: 350 }}
rowKey={(child) => 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 }),
},
]}
/>
</Card>
</Col>
</Row>
</Fragment>
);
}

View File

@ -0,0 +1,26 @@
import { ACCESS_SETTING, UserDataState } from '@pos/base';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
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');
}
}, []);
return <div>Setting</div>;
}

View File

@ -7,7 +7,7 @@ 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 (
@ -24,7 +24,7 @@ export default function App() {
>
<Routes>
<Route path="/auth/*" element={<AuthApp />} />
<Route path="/app" element={<PrivateApp />} />
<Route path="/app/*" element={<AppModule />} />
<Route path="/404" element={<NotFoundPage />} />
<Route path="/403" element={<ForbiddenAccessPage />} />
<Route path="*" element={<Navigate to="/app" />} />

View File

@ -10,3 +10,4 @@ export const DOWLOAD_POS_LINUX_DEB_URL = import.meta.env.VITE_DOWLOAD_POS_LINUX_
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;