Compare commits

...

4 Commits

Author SHA1 Message Date
Firman Ramdhani 01913a739f faet: setup base url local 2025-05-02 10:22:32 +07:00
Firman Ramdhani 685d14a7df feat: create feature local data configuration 2025-05-02 10:14:53 +07:00
Firman Ramdhani d49c487e61 feat: setup config local data 2025-04-30 11:57:30 +07:00
Firman Ramdhani 537d574af7 feat: setup config local data 2025-04-30 11:55:46 +07:00
9 changed files with 226 additions and 38 deletions

2
env/env.cloud vendored
View File

@ -1,3 +1,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.sky.eigen.co.id/api

2
env/env.development vendored
View File

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

View File

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

View File

@ -1,3 +1,5 @@
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_LOCAL=https://api.office.weplayground.id/api

View File

@ -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<any>();
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 (
<Modal
{...modalProps}
title="KONFIGURASI TAMPILAN DATA LOKAL"
width={800}
footer={[
<Button key="cancel" onClick={modalProps.onCancel} style={{ width: 100 }} disabled={loadingSave || loadingGet}>
Batal
</Button>,
<Button
key="submit"
type="primary"
disabled={loadingSave || loadingGet}
onClick={() => {
modal.confirm({
title: 'KONFIRMASI',
icon: null,
cancelText: 'Batal',
cancelButtonProps: { style: { width: 100 } },
okButtonProps: { style: { width: 100 } },
content: (
<div>
<div>Apakah Anda yakin ingin menyimpan konfigurasi ini?</div>
<div className="mb-4">Jumlah data yang akan ditampilkan di aplikasi lokal adalah: </div>
<div className={`text-center font-bold text-6xl mb-6 ${makeColorText(inputValue)}`}>
{inputValue}%
</div>
</div>
),
onOk: () => handleSave(inputValue),
});
}}
>
Simpan
</Button>,
]}
>
<div className="text-gray-600 italic mb-6">{`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.`}</div>
<div>
<Row gutter={[32, 8]}>
<Col span={24}>
<InputNumber
disabled={loadingSave || loadingGet}
min={0}
max={100}
value={inputValue}
onChange={onChange}
addonAfter="%"
/>
</Col>
<Col span={24}>
<Slider
min={0}
max={100}
disabled={loadingSave || loadingGet}
onChange={onChange}
value={typeof inputValue === 'number' ? inputValue : 0}
marks={{
0: '0%',
25: '25%',
50: '50%',
75: '75%',
100: '100%',
}}
/>
</Col>
</Row>
</div>
</Modal>
);
}

View File

@ -1,4 +1,4 @@
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';
@ -6,25 +6,30 @@ 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';
interface AdminLayoutProps {
children: ReactNode;
}
export default function AdminLayout(props: AdminLayoutProps) {
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);
}
}
@ -38,7 +43,6 @@ export default function AdminLayout(props: AdminLayoutProps) {
width: '100%',
display: 'flex',
alignItems: 'center',
// background: token.colorPrimary,
background: '#fff',
borderBottom: '1px solid #ddd',
}}
@ -46,27 +50,52 @@ export default function AdminLayout(props: AdminLayoutProps) {
<Flex style={{ width: '100%' }} align="center">
<Flex>
<Image src={Logo} width={50} preview={false} />
{/* <div style={{ fontWeight: 800, fontSize: 16 }}>WE POS</div> */}
</Flex>
<Flex style={{ marginLeft: 'auto' }}>
<Popconfirm
title="Are you sure want to logout?"
placement="bottomRight"
onConfirm={() => handleClickLogout()}
<Flex style={{ marginLeft: 'auto' }} gap={15} align="center">
<Dropdown
trigger={['click']}
overlayStyle={{ width: 150 }}
menu={{
items: [
{
key: '1',
label: 'Settings',
icon: <SettingOutlined />,
onClick: () => onOpenModalConfig(),
},
{
type: 'divider',
},
{
key: '2',
label: 'Logout',
icon: <LogoutOutlined />,
onClick: () => {
modal.confirm({
icon: null,
cancelText: 'Batal',
cancelButtonProps: { style: { width: 100 } },
okButtonProps: { style: { width: 100 } },
content: <div>Apakah anda yakin ingin keluar dari aplikasi?</div>,
onOk: () => handleClickLogout(),
});
},
},
],
}}
>
<Tooltip title="Logout" placement="bottom">
<Avatar size={35}>
{initialName ? (
<div style={{ fontSize: 13 }}>{initialName?.toUpperCase()}</div>
) : (
<FaUser style={{ fontSize: 14 }} />
)}
</Avatar>
</Tooltip>
</Popconfirm>
<Avatar size={35} style={{ cursor: 'pointer' }}>
{initialName ? (
<div style={{ fontSize: 13 }}>{initialName?.toUpperCase()}</div>
) : (
<FaUser style={{ fontSize: 14 }} />
)}
</Avatar>
</Dropdown>
</Flex>
</Flex>
</Header>
<LocalDataConfiguration open={openModalConfig} onCancel={onCancelModalConfig} />
<Content style={{ overflow: 'initial', background: '#fff', padding: 10 }}>{children}</Content>
</Layout>
);

View File

@ -1,6 +1,6 @@
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';
@ -14,21 +14,23 @@ export default function App() {
<RecoilRoot>
<DebugObserver />
<ConfigProvider theme={APP_THEME.LIGHT}>
<Suspense
fallback={
<Flex align="center" justify="center" style={{ height: '100vh' }}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 48 }} spin />} />
</Flex>
}
>
<Routes>
<Route path="/auth/*" element={<AuthApp />} />
<Route path="/app" element={<PrivateApp />} />
<Route path="/404" element={<NotFoundPage />} />
<Route path="/403" element={<ForbiddenAccessPage />} />
<Route path="*" element={<Navigate to="/app" />} />
</Routes>
</Suspense>
<AntdApp>
<Suspense
fallback={
<Flex align="center" justify="center" style={{ height: '100vh' }}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 48 }} spin />} />
</Flex>
}
>
<Routes>
<Route path="/auth/*" element={<AuthApp />} />
<Route path="/app" element={<PrivateApp />} />
<Route path="/404" element={<NotFoundPage />} />
<Route path="/403" element={<ForbiddenAccessPage />} />
<Route path="*" element={<Navigate to="/app" />} />
</Routes>
</Suspense>
</AntdApp>
</ConfigProvider>
</RecoilRoot>
);

View File

@ -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',
};

View File

@ -8,3 +8,5 @@ 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;