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_URL=http://103.187.147.241:30050/api
VITE_BASE_API_REPORT_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_REPORT_URL=https://api.sky.eigen.co.id/api
VITE_BASE_API_URL_LOCAL=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_URL=http://172.16.2.101:30050/api
VITE_BASE_API_REPORT_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_URL=http://103.187.147.241:30050/api
VITE_BASE_API_REPORT_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 AdminLayout from './layout';
import { API_URL, currencyFormatter } from '@pos/base'; import { Navigate, Route, Routes } from 'react-router-dom';
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';
export default function Admin() { const ReportModule = lazy(() => import('./pages/report'));
const [dataItem, setDataItem] = useState<any[]>([]); const SettingModule = lazy(() => import('./pages/setting'));
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]);
export default function AppModule() {
return ( return (
<AdminLayout> <AdminLayout>
<Row> <Routes>
<Col xl={8} lg={8} md={12} span={24}> <Route path="/report" element={<ReportModule />} />
<DatePicker <Route path="/setting" element={<SettingModule />} />
size="large" <Route path="/" element={<Navigate to="/app/report" />} />
popupStyle={{ fontSize: 16 }} <Route path="*" element={<Navigate to={'/404'} replace={true} />} />
allowClear={false} </Routes>
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>
</AdminLayout> </AdminLayout>
); );
} }

View File

@ -1,19 +1,21 @@
import { App, Avatar, Dropdown, Flex, Image, Layout } from 'antd'; import { App, Avatar, Dropdown, Flex, Image, Layout } from 'antd';
import { ReactNode, useState } from 'react'; import { ReactNode, useState } from 'react';
import { Content, Header } from 'antd/es/layout/layout'; 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 { FaUser } from 'react-icons/fa';
import axios from 'axios'; import axios from 'axios';
import Logo from '../../../base/presentation/assets/images/we-logo.png'; import Logo from '../../../base/presentation/assets/images/we-logo.png';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import LocalDataConfiguration from './components/local-data-configuration'; 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 { interface AdminLayoutProps {
children: ReactNode; children: ReactNode;
} }
export default function AdminLayout(props: AdminLayoutProps) { export default function AdminLayout(props: AdminLayoutProps) {
const navigate = useNavigate();
const { modal } = App.useApp(); const { modal } = App.useApp();
const { children } = props; const { children } = props;
@ -22,7 +24,7 @@ export default function AdminLayout(props: AdminLayoutProps) {
const [openModalConfig, setOpenModalConfig] = useState(false); const [openModalConfig, setOpenModalConfig] = useState(false);
const onCancelModalConfig = () => setOpenModalConfig(false); const onCancelModalConfig = () => setOpenModalConfig(false);
const onOpenModalConfig = () => setOpenModalConfig(true); // const onOpenModalConfig = () => setOpenModalConfig(true);
async function handleClickLogout() { async function handleClickLogout() {
try { try {
@ -35,11 +37,22 @@ export default function AdminLayout(props: AdminLayoutProps) {
function checkAllowAccessSetting() { function checkAllowAccessSetting() {
const username = user?.username; const username = user?.username;
const allowList = ['Endy', 'superadmin']; const accessSetting = ACCESS_SETTING ?? '';
const allowList = accessSetting.split('|');
if (allowList.includes(username)) return true; if (allowList.includes(username)) return true;
return false; return false;
} }
function gotoHome() {
navigate('/app');
}
function gotoSetting() {
// onOpenModalConfig();
navigate('/app/setting');
}
return ( return (
<Layout> <Layout>
<Header <Header
@ -56,7 +69,7 @@ export default function AdminLayout(props: AdminLayoutProps) {
> >
<Flex style={{ width: '100%' }} align="center"> <Flex style={{ width: '100%' }} align="center">
<Flex> <Flex>
<Image src={Logo} width={50} preview={false} /> <Image src={Logo} width={50} preview={false} onClick={gotoHome} style={{ cursor: 'pointer' }} />
</Flex> </Flex>
<Flex style={{ marginLeft: 'auto' }} gap={15} align="center"> <Flex style={{ marginLeft: 'auto' }} gap={15} align="center">
<Dropdown <Dropdown
@ -66,15 +79,26 @@ export default function AdminLayout(props: AdminLayoutProps) {
items: [ items: [
{ {
key: '1', key: '1',
label: 'Settings', label: 'Report',
icon: <SettingOutlined />, icon: <FileTextOutlined />,
onClick: () => onOpenModalConfig(), onClick: () => gotoHome(),
}, },
{ {
type: 'divider', type: 'divider',
key: 'divider_1',
}, },
{ {
key: '2', key: '2',
label: 'Settings',
icon: <SettingOutlined />,
onClick: () => gotoSetting(),
},
{
type: 'divider',
key: 'divider_2',
},
{
key: '3',
label: 'Logout', label: 'Logout',
icon: <LogoutOutlined />, icon: <LogoutOutlined />,
onClick: () => { onClick: () => {
@ -91,7 +115,7 @@ export default function AdminLayout(props: AdminLayoutProps) {
] ]
.map((item) => { .map((item) => {
const isAllowSetting = checkAllowAccessSetting(); 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 undefined;
} }
return item; 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'; import { LoadingOutlined } from '@ant-design/icons';
const AuthApp = lazy(() => import('./auth')); const AuthApp = lazy(() => import('./auth'));
const PrivateApp = lazy(() => import('./admin')); const AppModule = lazy(() => import('./admin/index'));
export default function App() { export default function App() {
return ( return (
@ -24,7 +24,7 @@ export default function App() {
> >
<Routes> <Routes>
<Route path="/auth/*" element={<AuthApp />} /> <Route path="/auth/*" element={<AuthApp />} />
<Route path="/app" element={<PrivateApp />} /> <Route path="/app/*" element={<AppModule />} />
<Route path="/404" element={<NotFoundPage />} /> <Route path="/404" element={<NotFoundPage />} />
<Route path="/403" element={<ForbiddenAccessPage />} /> <Route path="/403" element={<ForbiddenAccessPage />} />
<Route path="*" element={<Navigate to="/app" />} /> <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 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 BASE_API_URL_LOCAL = import.meta.env.VITE_BASE_API_URL_LOCAL;
export const ACCESS_SETTING = import.meta.env.VITE_BASE_ACCESS_SETTING;