feat: setup setting page
parent
29ff09d3aa
commit
4a08abe0b5
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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" />} />
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue