Implement dashboard, report page
This commit is contained in:
parent
40578d8b6a
commit
8ce84a940d
5669
cope2n-fe/package-lock.json
generated
5669
cope2n-fe/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
|||||||
"name": "sbt-ui",
|
"name": "sbt-ui",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run extract && npm run compile && vite",
|
"start": "npm run extract && npm run compile && vite --host",
|
||||||
"build": "npm run extract && npm run compile && tsc && vite build",
|
"build": "npm run extract && npm run compile && tsc && vite build",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"extract": "lingui extract --clean",
|
"extract": "lingui extract --clean",
|
||||||
@ -31,16 +31,10 @@
|
|||||||
"@ant-design/plots": "^1.2.3",
|
"@ant-design/plots": "^1.2.3",
|
||||||
"@ant-design/pro-layout": "^7.10.3",
|
"@ant-design/pro-layout": "^7.10.3",
|
||||||
"@babel/core": "^7.13.10",
|
"@babel/core": "^7.13.10",
|
||||||
"@casl/ability": "^6.3.3",
|
|
||||||
"@casl/react": "^3.1.0",
|
|
||||||
"@faker-js/faker": "^8.3.1",
|
|
||||||
"@heartexlabs/label-studio": "1.4.0",
|
|
||||||
"@tanstack/react-query": "^4.20.4",
|
"@tanstack/react-query": "^4.20.4",
|
||||||
"antd": "^5.4.0",
|
"antd": "^5.4.0",
|
||||||
"axios": "^1.2.2",
|
"axios": "^1.2.2",
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.1",
|
||||||
"dagre": "^0.8.5",
|
|
||||||
"faker": "^6.6.6",
|
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
@ -48,7 +42,6 @@
|
|||||||
"react-chartjs-2": "^5.2.0",
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.6.1",
|
"react-router-dom": "^6.6.1",
|
||||||
"reactflow": "^11.4.0",
|
|
||||||
"styled-components": "^5.3.6",
|
"styled-components": "^5.3.6",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
import {
|
|
||||||
AbilityBuilder,
|
|
||||||
AbilityClass,
|
|
||||||
MatchConditions,
|
|
||||||
PureAbility,
|
|
||||||
} from '@casl/ability';
|
|
||||||
import { Project, User } from 'models';
|
|
||||||
import { AppAbility } from './types';
|
|
||||||
|
|
||||||
const AppAbilityClass = PureAbility as AbilityClass<AppAbility>;
|
|
||||||
|
|
||||||
export const appAbilitiy = new AppAbilityClass([], {
|
|
||||||
conditionsMatcher: (matchConditions: MatchConditions) => matchConditions,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function updateAbility(ability: AppAbility, user: User) {
|
|
||||||
const { can, cannot, rules } = new AbilityBuilder(AppAbilityClass);
|
|
||||||
|
|
||||||
if (user.is_superuser) {
|
|
||||||
can('manage', 'all');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.is_superuser === false) {
|
|
||||||
can('read', 'Project');
|
|
||||||
// @ts-ignore
|
|
||||||
can<Project>(
|
|
||||||
['manage', 'update', 'delete', 'labeling:confirm', 'training:terminate'],
|
|
||||||
'Project',
|
|
||||||
(project: Project) => {
|
|
||||||
if (Array.isArray(project.admins)) {
|
|
||||||
if (project.admins.find((item) => item.id === user.id)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
cannot('create', 'Project');
|
|
||||||
}
|
|
||||||
|
|
||||||
// update ability
|
|
||||||
ability.update(rules);
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import { createContextualCan, useAbility } from '@casl/react';
|
|
||||||
import * as React from 'react';
|
|
||||||
import { appAbilitiy } from './ability';
|
|
||||||
|
|
||||||
export const AbilityContext = React.createContext(appAbilitiy);
|
|
||||||
export const Can = createContextualCan(AbilityContext.Consumer);
|
|
||||||
export const useAppAbility = () => useAbility(AbilityContext);
|
|
@ -1,3 +0,0 @@
|
|||||||
export * from './ability';
|
|
||||||
export * from './context';
|
|
||||||
export * from './types';
|
|
@ -1,36 +0,0 @@
|
|||||||
import { MatchConditions, PureAbility } from '@casl/ability';
|
|
||||||
import {
|
|
||||||
Category,
|
|
||||||
DataPointMetadata,
|
|
||||||
Deployment,
|
|
||||||
Device,
|
|
||||||
LogRecord,
|
|
||||||
Project,
|
|
||||||
RegisteredModel,
|
|
||||||
RegisteredProgram,
|
|
||||||
TrainingJob,
|
|
||||||
User,
|
|
||||||
} from 'models';
|
|
||||||
|
|
||||||
type InferSubjectAs<T, V extends string> = T | V;
|
|
||||||
export type AppSubjects =
|
|
||||||
| InferSubjectAs<Category, 'Category'>
|
|
||||||
| InferSubjectAs<DataPointMetadata, 'ImageMetadata'>
|
|
||||||
| InferSubjectAs<Deployment, 'Deployment'>
|
|
||||||
| InferSubjectAs<Device, 'Device'>
|
|
||||||
| InferSubjectAs<Project, 'Project'>
|
|
||||||
| InferSubjectAs<RegisteredModel, 'RegisteredModel'>
|
|
||||||
| InferSubjectAs<RegisteredProgram, 'RegisteredProgram'>
|
|
||||||
| InferSubjectAs<TrainingJob, 'TrainingJob'>
|
|
||||||
| InferSubjectAs<User, 'User'>
|
|
||||||
| InferSubjectAs<LogRecord, 'DeviceResult'>
|
|
||||||
| 'all';
|
|
||||||
|
|
||||||
export type CRUD = 'create' | 'read' | 'update' | 'delete';
|
|
||||||
export type ConfirmLabel = 'labeling:confirm';
|
|
||||||
export type TerminateJob = 'training:terminate';
|
|
||||||
|
|
||||||
export type AppActions = CRUD | ConfirmLabel | TerminateJob | 'manage';
|
|
||||||
export type Abilities = [AppActions, AppSubjects];
|
|
||||||
|
|
||||||
export type AppAbility = PureAbility<Abilities, MatchConditions>;
|
|
@ -3,7 +3,7 @@ import { t } from '@lingui/macro';
|
|||||||
import { Modal } from 'antd';
|
import { Modal } from 'antd';
|
||||||
import { ANT_PREFIX_CLASS } from 'consts';
|
import { ANT_PREFIX_CLASS } from 'consts';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
import { useBlocker } from 'react-router-dom';
|
||||||
|
|
||||||
function FormBlocker({ block }) {
|
function FormBlocker({ block }) {
|
||||||
const [isBlocking, setIsBlocking] = useState(false);
|
const [isBlocking, setIsBlocking] = useState(false);
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { AppstoreOutlined, BarChartOutlined } from '@ant-design/icons';
|
||||||
AppstoreOutlined,
|
|
||||||
BarChartOutlined,
|
|
||||||
FileSearchOutlined,
|
|
||||||
QuestionCircleOutlined,
|
|
||||||
RotateRightOutlined,
|
|
||||||
UsergroupAddOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Menu, MenuProps } from 'antd';
|
import { Menu, MenuProps } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -41,10 +34,10 @@ function LeftMenu() {
|
|||||||
|
|
||||||
const generalSubItems = [
|
const generalSubItems = [
|
||||||
getItem(t`Dashboard`, '/dashboard', <AppstoreOutlined />),
|
getItem(t`Dashboard`, '/dashboard', <AppstoreOutlined />),
|
||||||
getItem(t`Inference`, '/inference', <RotateRightOutlined />),
|
// getItem(t`Inference`, '/inference', <RotateRightOutlined />),
|
||||||
getItem(t`Reviews`, '/reviews', <FileSearchOutlined />),
|
// getItem(t`Reviews`, '/reviews', <FileSearchOutlined />),
|
||||||
getItem(t`Reports`, '/reports', <BarChartOutlined />),
|
getItem(t`Reports`, '/reports', <BarChartOutlined />),
|
||||||
getItem(t`Users`, '/users', <UsergroupAddOutlined />),
|
// getItem(t`Users`, '/users', <UsergroupAddOutlined />),
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -62,7 +55,7 @@ function LeftMenu() {
|
|||||||
style={{ borderRight: 'none' }}
|
style={{ borderRight: 'none' }}
|
||||||
items={generalSubItems}
|
items={generalSubItems}
|
||||||
/>
|
/>
|
||||||
<div
|
{/* <div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@ -74,7 +67,7 @@ function LeftMenu() {
|
|||||||
>
|
>
|
||||||
<QuestionCircleOutlined />
|
<QuestionCircleOutlined />
|
||||||
<span>Help</span>
|
<span>Help</span>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { useGlobalState, useLocalStorage } from 'utils/hooks';
|
import { useGlobalState, useLocalStorage } from 'utils/hooks';
|
||||||
|
|
||||||
const UserHeader = () => {
|
const UserHeader = () => {
|
||||||
const { user, logOut } = useAuth();
|
const { logOut } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [, setLocaleStorage] = useLocalStorage('sbt-locale', DEFAULT_LOCALE);
|
const [, setLocaleStorage] = useLocalStorage('sbt-locale', DEFAULT_LOCALE);
|
||||||
const { data: locale, setData: setLocale } = useGlobalState(
|
const { data: locale, setData: setLocale } = useGlobalState(
|
||||||
@ -19,10 +19,6 @@ const UserHeader = () => {
|
|||||||
DEFAULT_LOCALE,
|
DEFAULT_LOCALE,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userMenu = (
|
const userMenu = (
|
||||||
<Menu className='sbt-header-menu' selectedKeys={[locale]}>
|
<Menu className='sbt-header-menu' selectedKeys={[locale]}>
|
||||||
<Menu.SubMenu
|
<Menu.SubMenu
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export { default as ReportDetailTable } from './report-detail-table';
|
|
||||||
export { default as ReportInformation } from './report-information';
|
export { default as ReportInformation } from './report-information';
|
||||||
export { default as ReportMultiTypeChart } from './report-multitype-chart';
|
export { default as ReportMultiTypeChart } from './report-multitype-chart';
|
||||||
|
export { default as ReportOverViewTable } from './report-overview-table';
|
||||||
export * from './report-pie-chart';
|
export * from './report-pie-chart';
|
||||||
|
@ -1,319 +0,0 @@
|
|||||||
import type { TableColumnsType } from 'antd';
|
|
||||||
import { Table } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface DataType {
|
|
||||||
key: React.Key;
|
|
||||||
subSidiaries: string;
|
|
||||||
extractionDate: string | Date;
|
|
||||||
snOrImeiNumber: number;
|
|
||||||
invoiceNumber: number;
|
|
||||||
totalImages: number;
|
|
||||||
successfulNumber: number;
|
|
||||||
successfulPercentage: number;
|
|
||||||
badNumber: number;
|
|
||||||
badPercentage: number;
|
|
||||||
snImeiAAR: number; // AAR: Average Accuracy Rate
|
|
||||||
purchaseDateAAR: number;
|
|
||||||
retailerNameAAR: number;
|
|
||||||
snImeiAPT: number; // APT: Average Processing Time
|
|
||||||
invoiceAPT: number;
|
|
||||||
snImeiTC: number; // TC: transaction count
|
|
||||||
invoiceTC: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns: TableColumnsType<DataType> = [
|
|
||||||
{
|
|
||||||
title: 'Subsidiaries',
|
|
||||||
dataIndex: 'subSidiaries',
|
|
||||||
key: 'subSidiaries',
|
|
||||||
fixed: 'left',
|
|
||||||
width: '100px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'OCR extraction date',
|
|
||||||
dataIndex: 'extractionDate',
|
|
||||||
key: 'extractionDate',
|
|
||||||
fixed: 'left',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'OCR Images',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: 'SN/IMEI',
|
|
||||||
dataIndex: 'snOrImeiNumber',
|
|
||||||
key: 'snOrImeiNumber',
|
|
||||||
width: '50px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Invoice',
|
|
||||||
dataIndex: 'invoiceNumber',
|
|
||||||
key: 'invoiceNumber',
|
|
||||||
width: '50px',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Total Images',
|
|
||||||
dataIndex: 'totalImages',
|
|
||||||
key: 'totalImages',
|
|
||||||
fixed: 'left',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Image Quality',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: 'Successful',
|
|
||||||
dataIndex: 'successfulNumber',
|
|
||||||
key: 'successfulNumber',
|
|
||||||
width: '50px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '% Successful',
|
|
||||||
dataIndex: 'successfulPercentage',
|
|
||||||
key: 'successfulPercentage',
|
|
||||||
width: '120px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Bad',
|
|
||||||
dataIndex: 'badNumber',
|
|
||||||
key: 'badNumber',
|
|
||||||
width: '30px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '% Bad',
|
|
||||||
dataIndex: 'badPercentage',
|
|
||||||
key: 'badPercentage',
|
|
||||||
width: '60px',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: 'Average accuracy rate (%) \n(※ character-based)',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: 'IMEI / Serial no.',
|
|
||||||
dataIndex: 'snImeiAAR',
|
|
||||||
key: 'snImeiAAR',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Purchase date',
|
|
||||||
dataIndex: 'purchaseDateAAR',
|
|
||||||
key: 'purchaseDateAAR',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Retailer name',
|
|
||||||
dataIndex: 'retailerNameAAR',
|
|
||||||
key: 'retailerNameAAR',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Average Processing Time',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: 'SN/IMEI',
|
|
||||||
dataIndex: 'snImeiAPT',
|
|
||||||
key: 'snImeiAPT',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Invoice',
|
|
||||||
dataIndex: 'invoiceAPT',
|
|
||||||
key: 'invoiceAPT',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Usage (Transaction Count)',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: 'SN/IMEI',
|
|
||||||
dataIndex: 'snImeiTC',
|
|
||||||
key: 'snImeiTC',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Invoice',
|
|
||||||
dataIndex: 'invoiceTC',
|
|
||||||
key: 'invoiceTC',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const data: DataType[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
data.push({
|
|
||||||
key: i,
|
|
||||||
subSidiaries: '',
|
|
||||||
extractionDate: 'SubTotal (Jan)',
|
|
||||||
snOrImeiNumber: null,
|
|
||||||
invoiceNumber: null,
|
|
||||||
totalImages: Math.floor(Math.random() * 100),
|
|
||||||
successfulNumber: Math.floor(Math.random() * 100),
|
|
||||||
successfulPercentage: Math.floor(Math.random() * 100),
|
|
||||||
badNumber: Math.floor(Math.random() * 100),
|
|
||||||
badPercentage: Math.floor(Math.random() * 100),
|
|
||||||
snImeiAAR: null,
|
|
||||||
purchaseDateAAR: null,
|
|
||||||
retailerNameAAR: null,
|
|
||||||
snImeiAPT: null,
|
|
||||||
invoiceAPT: null,
|
|
||||||
snImeiTC: Math.floor(Math.random() * 100),
|
|
||||||
invoiceTC: Math.floor(Math.random() * 100),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const expandedRowRender = () => {
|
|
||||||
const subData = [];
|
|
||||||
for (let i = 0; i < 5; ++i) {
|
|
||||||
subData.push({
|
|
||||||
key: i,
|
|
||||||
subSidiaries: 'SESP',
|
|
||||||
extractionDate: 'Jan',
|
|
||||||
snOrImeiNumber: Math.floor(Math.random() * 100),
|
|
||||||
invoiceNumber: Math.floor(Math.random() * 100),
|
|
||||||
totalImages: Math.floor(Math.random() * 100),
|
|
||||||
successfulNumber: Math.floor(Math.random() * 100),
|
|
||||||
successfulPercentage: Math.floor(Math.random() * 100),
|
|
||||||
badNumber: Math.floor(Math.random() * 100),
|
|
||||||
badPercentage: Math.floor(Math.random() * 100),
|
|
||||||
snImeiAAR: Math.floor(Math.random() * 100),
|
|
||||||
purchaseDateAAR: Math.floor(Math.random() * 100),
|
|
||||||
retailerNameAAR: Math.floor(Math.random() * 100),
|
|
||||||
snImeiAPT: Math.floor(Math.random() * 100),
|
|
||||||
invoiceAPT: Math.floor(Math.random() * 100),
|
|
||||||
snImeiTC: Math.floor(Math.random() * 100),
|
|
||||||
invoiceTC: Math.floor(Math.random() * 100),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const subColumns: TableColumnsType<DataType> = [
|
|
||||||
{
|
|
||||||
title: 'Subsidiaries',
|
|
||||||
dataIndex: 'subSidiaries',
|
|
||||||
key: 'subSidiaries',
|
|
||||||
fixed: 'left',
|
|
||||||
width: '100px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'OCR extraction date',
|
|
||||||
dataIndex: 'extractionDate',
|
|
||||||
key: 'extractionDate',
|
|
||||||
fixed: 'left',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'SN/IMEI',
|
|
||||||
dataIndex: 'snOrImeiNumber',
|
|
||||||
key: 'snOrImeiNumber',
|
|
||||||
width: '50px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Invoice',
|
|
||||||
dataIndex: 'invoiceNumber',
|
|
||||||
key: 'invoiceNumber',
|
|
||||||
width: '50px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Total Images',
|
|
||||||
dataIndex: 'totalImages',
|
|
||||||
key: 'totalImages',
|
|
||||||
fixed: 'left',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Successful',
|
|
||||||
dataIndex: 'successfulNumber',
|
|
||||||
key: 'successfulNumber',
|
|
||||||
width: '50px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '% Successful',
|
|
||||||
dataIndex: 'successfulPercentage',
|
|
||||||
key: 'successfulPercentage',
|
|
||||||
width: '120px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Bad',
|
|
||||||
dataIndex: 'badNumber',
|
|
||||||
key: 'badNumber',
|
|
||||||
width: '30px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '% Bad',
|
|
||||||
dataIndex: 'badPercentage',
|
|
||||||
key: 'badPercentage',
|
|
||||||
width: '60px',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: 'IMEI / Serial no.',
|
|
||||||
dataIndex: 'snImeiAAR',
|
|
||||||
key: 'snImeiAAR',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Purchase date',
|
|
||||||
dataIndex: 'purchaseDateAAR',
|
|
||||||
key: 'purchaseDateAAR',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Retailer name',
|
|
||||||
dataIndex: 'retailerNameAAR',
|
|
||||||
key: 'retailerNameAAR',
|
|
||||||
width: '130px',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: 'SN/IMEI',
|
|
||||||
dataIndex: 'snImeiAPT',
|
|
||||||
key: 'snImeiAPT',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Invoice',
|
|
||||||
dataIndex: 'invoiceAPT',
|
|
||||||
key: 'invoiceAPT',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'SN/IMEI',
|
|
||||||
dataIndex: 'snImeiTC',
|
|
||||||
key: 'snImeiTC',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Invoice',
|
|
||||||
dataIndex: 'invoiceTC',
|
|
||||||
key: 'invoiceTC',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<Table
|
|
||||||
columns={subColumns}
|
|
||||||
dataSource={subData}
|
|
||||||
pagination={false}
|
|
||||||
bordered
|
|
||||||
// showHeader={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ReportDetailTable: React.FC = () => (
|
|
||||||
<>
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
dataSource={data}
|
|
||||||
bordered
|
|
||||||
size='small'
|
|
||||||
expandable={{ expandedRowRender, defaultExpandedRowKeys: [0, 1] }}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ReportDetailTable;
|
|
465
cope2n-fe/src/components/report-detail/report-overview-table.tsx
Normal file
465
cope2n-fe/src/components/report-detail/report-overview-table.tsx
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
import type { TableColumnsType } from 'antd';
|
||||||
|
import { Table } from 'antd';
|
||||||
|
import { useOverViewReport } from 'queries/report';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
key: React.Key;
|
||||||
|
subSidiaries: string;
|
||||||
|
extractionDate: string | Date;
|
||||||
|
snOrImeiNumber: number;
|
||||||
|
invoiceNumber: number;
|
||||||
|
totalImages: number;
|
||||||
|
successfulNumber: number;
|
||||||
|
successfulPercentage: number;
|
||||||
|
badNumber: number;
|
||||||
|
badPercentage: number;
|
||||||
|
snImeiAAR: number; // AAR: Average Accuracy Rate
|
||||||
|
purchaseDateAAR: number;
|
||||||
|
retailerNameAAR: number;
|
||||||
|
snImeiAPT: number; // APT: Average Processing Time
|
||||||
|
invoiceAPT: number;
|
||||||
|
snImeiTC: number; // TC: transaction count
|
||||||
|
invoiceTC: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: TableColumnsType<DataType> = [
|
||||||
|
{
|
||||||
|
title: 'Subs',
|
||||||
|
dataIndex: 'subSidiaries',
|
||||||
|
key: 'subSidiaries',
|
||||||
|
width: '100px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'OCR extraction date',
|
||||||
|
dataIndex: 'extractionDate',
|
||||||
|
key: 'extractionDate',
|
||||||
|
width: '130px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'OCR Images',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'SN/IMEI',
|
||||||
|
dataIndex: 'snOrImeiNumber',
|
||||||
|
key: 'snOrImeiNumber',
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Invoice',
|
||||||
|
dataIndex: 'invoiceNumber',
|
||||||
|
key: 'invoiceNumber',
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Total Images',
|
||||||
|
dataIndex: 'totalImages',
|
||||||
|
key: 'totalImages',
|
||||||
|
width: '130px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Image Quality',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'Successful',
|
||||||
|
dataIndex: 'successfulNumber',
|
||||||
|
key: 'successfulNumber',
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '% Successful',
|
||||||
|
dataIndex: 'successfulPercentage',
|
||||||
|
key: 'successfulPercentage',
|
||||||
|
width: '120px',
|
||||||
|
render: (_, record) => {
|
||||||
|
return <span>{(record.successfulPercentage * 100).toFixed(2)}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Bad',
|
||||||
|
dataIndex: 'badNumber',
|
||||||
|
key: 'badNumber',
|
||||||
|
width: '30px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '% Bad',
|
||||||
|
dataIndex: 'badPercentage',
|
||||||
|
key: 'badPercentage',
|
||||||
|
width: '60px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.badPercentage * 100 > 10;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{(record.badPercentage * 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'Average accuracy rate (%) \n(※ character-based)',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'IMEI / Serial no.',
|
||||||
|
dataIndex: 'snImeiAAR',
|
||||||
|
key: 'snImeiAAR',
|
||||||
|
width: '130px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.snImeiAAR * 100 < 98;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{(record.snImeiAAR * 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Purchase date',
|
||||||
|
dataIndex: 'purchaseDateAAR',
|
||||||
|
key: 'purchaseDateAAR',
|
||||||
|
width: '130px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.purchaseDateAAR * 100 < 98;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{(record.purchaseDateAAR * 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Retailer name',
|
||||||
|
dataIndex: 'retailerNameAAR',
|
||||||
|
key: 'retailerNameAAR',
|
||||||
|
width: '130px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.retailerNameAAR * 100 < 98;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{(record.retailerNameAAR * 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Average Processing Time',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'SN/IMEI',
|
||||||
|
dataIndex: 'snImeiAPT',
|
||||||
|
key: 'snImeiAPT',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.snImeiAPT > 2;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{record.snImeiAPT.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Invoice',
|
||||||
|
dataIndex: 'invoiceAPT',
|
||||||
|
key: 'invoiceAPT',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.invoiceAPT > 2;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{record.invoiceAPT.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Usage (Transaction Count)',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'SN/IMEI',
|
||||||
|
dataIndex: 'snImeiTC',
|
||||||
|
key: 'snImeiTC',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Invoice',
|
||||||
|
dataIndex: 'invoiceTC',
|
||||||
|
key: 'invoiceTC',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ReportOverViewTableProps {
|
||||||
|
pagination: {
|
||||||
|
page: number;
|
||||||
|
page_size: number;
|
||||||
|
};
|
||||||
|
setPagination: React.Dispatch<
|
||||||
|
React.SetStateAction<{
|
||||||
|
page: number;
|
||||||
|
page_size: number;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
isLoading: boolean;
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReportOverViewTable: React.FC = () => {
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
});
|
||||||
|
const { isLoading, data } = useOverViewReport({
|
||||||
|
page: pagination.page,
|
||||||
|
});
|
||||||
|
const overviewDataResponse = data as any;
|
||||||
|
const dataSubsRows = overviewDataResponse?.overview_data
|
||||||
|
.map((item, index) => {
|
||||||
|
if (item.subs.includes('+')) {
|
||||||
|
return {
|
||||||
|
key: index,
|
||||||
|
subSidiaries: '',
|
||||||
|
extractionDate: item.extraction_date,
|
||||||
|
snOrImeiNumber: '',
|
||||||
|
invoiceNumber: '',
|
||||||
|
totalImages: item.total_images,
|
||||||
|
successfulNumber: item.images_quality.successful,
|
||||||
|
successfulPercentage: item.images_quality.successful_percent,
|
||||||
|
badNumber: item.images_quality.bad,
|
||||||
|
badPercentage: item.images_quality.bad_percent,
|
||||||
|
snImeiAAR: item.average_accuracy_rate.imei,
|
||||||
|
purchaseDateAAR: item.average_accuracy_rate.purchase_date,
|
||||||
|
retailerNameAAR: item.average_accuracy_rate.retailer_name,
|
||||||
|
snImeiAPT: item.average_processing_time.imei,
|
||||||
|
invoiceAPT: item.average_processing_time.invoice,
|
||||||
|
snImeiTC: item.usage.imei,
|
||||||
|
invoiceTC: item.usage.invoice,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item) => item);
|
||||||
|
|
||||||
|
const expandedRowRender = () => {
|
||||||
|
const subData = overviewDataResponse?.overview_data
|
||||||
|
.map((item, index) => {
|
||||||
|
if (!item.subs.includes('+')) {
|
||||||
|
return {
|
||||||
|
key: index,
|
||||||
|
subSidiaries: item.subs,
|
||||||
|
extractionDate: item.extraction_date,
|
||||||
|
snOrImeiNumber: item.num_imei,
|
||||||
|
invoiceNumber: item.num_invoice,
|
||||||
|
totalImages: item.total_images,
|
||||||
|
successfulNumber: item.images_quality.successful,
|
||||||
|
successfulPercentage: item.images_quality.successful_percent,
|
||||||
|
badNumber: item.images_quality.bad,
|
||||||
|
badPercentage: item.images_quality.bad_percent,
|
||||||
|
snImeiAAR: item.average_accuracy_rate.imei,
|
||||||
|
purchaseDateAAR: item.average_accuracy_rate.purchase_date,
|
||||||
|
retailerNameAAR: item.average_accuracy_rate.retailer_name,
|
||||||
|
snImeiAPT: item.average_processing_time.imei,
|
||||||
|
invoiceAPT: item.average_processing_time.invoice,
|
||||||
|
snImeiTC: item.usage.imei,
|
||||||
|
invoiceTC: item.usage.invoice,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item) => item);
|
||||||
|
|
||||||
|
const subColumns: TableColumnsType<DataType> = [
|
||||||
|
{
|
||||||
|
title: 'Subs',
|
||||||
|
dataIndex: 'subSidiaries',
|
||||||
|
key: 'subSidiaries',
|
||||||
|
width: '100px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'OCR extraction date',
|
||||||
|
dataIndex: 'extractionDate',
|
||||||
|
key: 'extractionDate',
|
||||||
|
width: '130px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'SN/IMEI',
|
||||||
|
dataIndex: 'snOrImeiNumber',
|
||||||
|
key: 'snOrImeiNumber',
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Invoice',
|
||||||
|
dataIndex: 'invoiceNumber',
|
||||||
|
key: 'invoiceNumber',
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Total Images',
|
||||||
|
dataIndex: 'totalImages',
|
||||||
|
key: 'totalImages',
|
||||||
|
width: '130px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Successful',
|
||||||
|
dataIndex: 'successfulNumber',
|
||||||
|
key: 'successfulNumber',
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '% Successful',
|
||||||
|
dataIndex: 'successfulPercentage',
|
||||||
|
key: 'successfulPercentage',
|
||||||
|
width: '120px',
|
||||||
|
render: (_, record) => {
|
||||||
|
return <span>{(record.successfulPercentage * 100).toFixed(2)}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Bad',
|
||||||
|
dataIndex: 'badNumber',
|
||||||
|
key: 'badNumber',
|
||||||
|
width: '30px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '% Bad',
|
||||||
|
dataIndex: 'badPercentage',
|
||||||
|
key: 'badPercentage',
|
||||||
|
width: '60px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.badPercentage * 100 > 10;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{(record.badPercentage * 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'IMEI / Serial no.',
|
||||||
|
dataIndex: 'snImeiAAR',
|
||||||
|
key: 'snImeiAAR',
|
||||||
|
width: '130px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.snImeiAAR * 100 < 98;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{(record.snImeiAAR * 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Purchase date',
|
||||||
|
dataIndex: 'purchaseDateAAR',
|
||||||
|
key: 'purchaseDateAAR',
|
||||||
|
width: '130px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.purchaseDateAAR * 100 < 98;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{(record.purchaseDateAAR * 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Retailer name',
|
||||||
|
dataIndex: 'retailerNameAAR',
|
||||||
|
key: 'retailerNameAAR',
|
||||||
|
width: '130px',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.retailerNameAAR * 100 < 98;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{(record.retailerNameAAR * 100).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'SN/IMEI',
|
||||||
|
dataIndex: 'snImeiAPT',
|
||||||
|
key: 'snImeiAPT',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.snImeiAPT > 2;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{record.snImeiAPT.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Invoice',
|
||||||
|
dataIndex: 'invoiceAPT',
|
||||||
|
key: 'invoiceAPT',
|
||||||
|
render: (_, record) => {
|
||||||
|
const isAbnormal = record.invoiceAPT > 2;
|
||||||
|
return (
|
||||||
|
<span style={{ color: isAbnormal ? 'red' : '' }}>
|
||||||
|
{record.invoiceAPT.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'SN/IMEI',
|
||||||
|
dataIndex: 'snImeiTC',
|
||||||
|
key: 'snImeiTC',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Invoice',
|
||||||
|
dataIndex: 'invoiceTC',
|
||||||
|
key: 'invoiceTC',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
columns={subColumns}
|
||||||
|
dataSource={subData}
|
||||||
|
pagination={false}
|
||||||
|
bordered
|
||||||
|
// showHeader={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
loading={isLoading}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSubsRows}
|
||||||
|
bordered
|
||||||
|
size='small'
|
||||||
|
expandable={{ expandedRowRender, defaultExpandedRowKeys: [0, 1] }}
|
||||||
|
scroll={{ x: 2000 }}
|
||||||
|
pagination={{
|
||||||
|
current: pagination.page,
|
||||||
|
pageSize: pagination.page_size,
|
||||||
|
total: overviewDataResponse?.page.count,
|
||||||
|
showTotal: (total, range) =>
|
||||||
|
`${range[0]}-${range[1]} of ${total} items`,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
setPagination({
|
||||||
|
page,
|
||||||
|
page_size: pageSize || 10,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showSizeChanger: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportOverViewTable;
|
@ -13,7 +13,7 @@ const ReportTable: React.FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [pagination, setPagination] = useState(() => ({
|
const [pagination, setPagination] = useState(() => ({
|
||||||
page: reportData?.page.total_pages || 1,
|
page: report_data?.page.total_pages || 1,
|
||||||
page_size: 10,
|
page_size: 10,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { AbilityContext, appAbilitiy } from 'acl';
|
|
||||||
import Internationalization from 'components/internaltionalization';
|
import Internationalization from 'components/internaltionalization';
|
||||||
import { GlobalSpin } from 'components/spin';
|
import { GlobalSpin } from 'components/spin';
|
||||||
import { queryClient } from 'queries';
|
import { queryClient } from 'queries';
|
||||||
@ -16,9 +15,9 @@ function App() {
|
|||||||
<Suspense fallback={<GlobalSpin />}>
|
<Suspense fallback={<GlobalSpin />}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Internationalization>
|
<Internationalization>
|
||||||
<AbilityContext.Provider value={appAbilitiy}>
|
{/* <AbilityContext.Provider value={appAbilitiy}> */}
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</AbilityContext.Provider>
|
{/* </AbilityContext.Provider> */}
|
||||||
</Internationalization>
|
</Internationalization>
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
@ -25,10 +25,10 @@ export const MainLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
? JSON.parse(localStorage.getItem('user-name'))
|
? JSON.parse(localStorage.getItem('user-name'))
|
||||||
: '';
|
: '';
|
||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
{
|
// {
|
||||||
key: 'user_management',
|
// key: 'user_management',
|
||||||
label: <>User Management</>,
|
// label: <>User Management</>,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
key: 'logout',
|
key: 'logout',
|
||||||
label: <>Logout</>,
|
label: <>Logout</>,
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
"Email format is not correct": "Email format is not correct",
|
"Email format is not correct": "Email format is not correct",
|
||||||
"English": "English",
|
"English": "English",
|
||||||
"Go to Report page": "Go to Report page",
|
"Go to Report page": "Go to Report page",
|
||||||
"Inference": "Inference",
|
|
||||||
"Language": "Language",
|
"Language": "Language",
|
||||||
"Login": "Login",
|
"Login": "Login",
|
||||||
"Logout": "Logout",
|
"Logout": "Logout",
|
||||||
@ -24,10 +23,9 @@
|
|||||||
"Please enter a valid domain": "Please enter a valid domain",
|
"Please enter a valid domain": "Please enter a valid domain",
|
||||||
"Please specify a password": "Please specify a password",
|
"Please specify a password": "Please specify a password",
|
||||||
"Please specify a username": "Please specify a username",
|
"Please specify a username": "Please specify a username",
|
||||||
"Report": "Report",
|
"Report {0}...": "Report {0}...",
|
||||||
"Reports": "Reports",
|
"Reports": "Reports",
|
||||||
"Retry": "Retry",
|
"Retry": "Retry",
|
||||||
"Reviews": "Reviews",
|
|
||||||
"Service temporarily unavailable.": "Service temporarily unavailable.",
|
"Service temporarily unavailable.": "Service temporarily unavailable.",
|
||||||
"Something went wrong.": "Something went wrong.",
|
"Something went wrong.": "Something went wrong.",
|
||||||
"Sorry, something went wrong.": "Sorry, something went wrong.",
|
"Sorry, something went wrong.": "Sorry, something went wrong.",
|
||||||
@ -40,12 +38,12 @@
|
|||||||
"User log in successfully": "User log in successfully",
|
"User log in successfully": "User log in successfully",
|
||||||
"Username": "Username",
|
"Username": "Username",
|
||||||
"Username must not have more than {MAX_USERNAME_LENGTH} characters": "Username must not have more than {MAX_USERNAME_LENGTH} characters",
|
"Username must not have more than {MAX_USERNAME_LENGTH} characters": "Username must not have more than {MAX_USERNAME_LENGTH} characters",
|
||||||
"Users": "Users",
|
|
||||||
"Vietnamese": "Vietnamese",
|
"Vietnamese": "Vietnamese",
|
||||||
"You are not allowed to upload file bigger than {0}MB.": "You are not allowed to upload file bigger than {0}MB.",
|
"You are not allowed to upload file bigger than {0}MB.": "You are not allowed to upload file bigger than {0}MB.",
|
||||||
"You are not allowed to upload image bigger than {0}MB.": "You are not allowed to upload image bigger than {0}MB.",
|
"You are not allowed to upload image bigger than {0}MB.": "You are not allowed to upload image bigger than {0}MB.",
|
||||||
"You are only allowed to upload .zip file.": "You are only allowed to upload .zip file.",
|
"You are only allowed to upload .zip file.": "You are only allowed to upload .zip file.",
|
||||||
"You are only allowed to upload image or .zip files.": "You are only allowed to upload image or .zip files.",
|
"You are only allowed to upload image or .zip files.": "You are only allowed to upload image or .zip files.",
|
||||||
"You are only allowed to upload {0} file.": "You are only allowed to upload {0} file.",
|
"You are only allowed to upload {0} file.": "You are only allowed to upload {0} file.",
|
||||||
"You have unsaved changes!": "You have unsaved changes!"
|
"You have unsaved changes!": "You have unsaved changes!",
|
||||||
|
"Your current password has expired. Please change your password to continue.": "Your current password has expired. Please change your password to continue."
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
"Email format is not correct": "Định dạng email không hợp lệ",
|
"Email format is not correct": "Định dạng email không hợp lệ",
|
||||||
"English": "Tiếng Anh",
|
"English": "Tiếng Anh",
|
||||||
"Go to Report page": "",
|
"Go to Report page": "",
|
||||||
"Inference": "",
|
|
||||||
"Language": "Ngôn ngữ",
|
"Language": "Ngôn ngữ",
|
||||||
"Login": "Đăng nhập",
|
"Login": "Đăng nhập",
|
||||||
"Logout": "Đăng xuất",
|
"Logout": "Đăng xuất",
|
||||||
@ -24,10 +23,9 @@
|
|||||||
"Please enter a valid domain": "Vui lòng nhập một tên miền hợp lệ",
|
"Please enter a valid domain": "Vui lòng nhập một tên miền hợp lệ",
|
||||||
"Please specify a password": "Vui lòng nhập một mật khẩu",
|
"Please specify a password": "Vui lòng nhập một mật khẩu",
|
||||||
"Please specify a username": "Vui lòng nhập một tên tài khoản",
|
"Please specify a username": "Vui lòng nhập một tên tài khoản",
|
||||||
"Report": "",
|
"Report {0}...": "",
|
||||||
"Reports": "",
|
"Reports": "",
|
||||||
"Retry": "Thử lại",
|
"Retry": "Thử lại",
|
||||||
"Reviews": "",
|
|
||||||
"Service temporarily unavailable.": "Dịch vụ máy chủ hiện tại không sẵn sàng.",
|
"Service temporarily unavailable.": "Dịch vụ máy chủ hiện tại không sẵn sàng.",
|
||||||
"Something went wrong.": "Có lỗi xảy ra",
|
"Something went wrong.": "Có lỗi xảy ra",
|
||||||
"Sorry, something went wrong.": "Hệ thống gặp lỗi",
|
"Sorry, something went wrong.": "Hệ thống gặp lỗi",
|
||||||
@ -40,12 +38,12 @@
|
|||||||
"User log in successfully": "Đăng nhập thành công",
|
"User log in successfully": "Đăng nhập thành công",
|
||||||
"Username": "Tên tài khoản",
|
"Username": "Tên tài khoản",
|
||||||
"Username must not have more than {MAX_USERNAME_LENGTH} characters": "Tên tài khoản không được chứa nhiều hơn {MAX_USERNAME_LENGTH} kí tự",
|
"Username must not have more than {MAX_USERNAME_LENGTH} characters": "Tên tài khoản không được chứa nhiều hơn {MAX_USERNAME_LENGTH} kí tự",
|
||||||
"Users": "Người dùng",
|
|
||||||
"Vietnamese": "Tiếng Việt",
|
"Vietnamese": "Tiếng Việt",
|
||||||
"You are not allowed to upload file bigger than {0}MB.": "Bạn không được phép tải lên tệp lớn hơn {0} MB.",
|
"You are not allowed to upload file bigger than {0}MB.": "Bạn không được phép tải lên tệp lớn hơn {0} MB.",
|
||||||
"You are not allowed to upload image bigger than {0}MB.": "Bạn không được phép tải lên hình ảnh lớn hơn {0} MB.",
|
"You are not allowed to upload image bigger than {0}MB.": "Bạn không được phép tải lên hình ảnh lớn hơn {0} MB.",
|
||||||
"You are only allowed to upload .zip file.": "Bạn chỉ được phép tải lên tập tin .zip.",
|
"You are only allowed to upload .zip file.": "Bạn chỉ được phép tải lên tập tin .zip.",
|
||||||
"You are only allowed to upload image or .zip files.": "Bạn chỉ được phép tải lên hình ảnh hoặc .zip.",
|
"You are only allowed to upload image or .zip files.": "Bạn chỉ được phép tải lên hình ảnh hoặc .zip.",
|
||||||
"You are only allowed to upload {0} file.": "Bạn chỉ được phép tải lên {0}.",
|
"You are only allowed to upload {0} file.": "Bạn chỉ được phép tải lên {0}.",
|
||||||
"You have unsaved changes!": "Bạn có những thay đổi chưa được lưu!"
|
"You have unsaved changes!": "Bạn có những thay đổi chưa được lưu!",
|
||||||
|
"Your current password has expired. Please change your password to continue.": ""
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ export type ReportListParams = {
|
|||||||
page_size?: number;
|
page_size?: number;
|
||||||
start_date?: string;
|
start_date?: string;
|
||||||
end_date?: string;
|
end_date?: string;
|
||||||
|
subsidiary?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface MakeReportResponse {
|
export interface MakeReportResponse {
|
||||||
@ -86,3 +87,44 @@ export interface Page {
|
|||||||
total_pages: number;
|
total_pages: number;
|
||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overview report type
|
||||||
|
export interface OverViewDataResponse {
|
||||||
|
overview_data: OverviewData[];
|
||||||
|
page: Page;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OverviewData {
|
||||||
|
subs: string;
|
||||||
|
extraction_date: string;
|
||||||
|
total_images: number;
|
||||||
|
images_quality: ImagesQuality;
|
||||||
|
average_accuracy_rate: AverageAccuracyRate;
|
||||||
|
average_processing_time: AverageProcessingTime;
|
||||||
|
usage: Usage;
|
||||||
|
num_imei?: number;
|
||||||
|
num_invoice?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImagesQuality {
|
||||||
|
successful: number;
|
||||||
|
successful_percent: number;
|
||||||
|
bad: number;
|
||||||
|
bad_percent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AverageAccuracyRate {
|
||||||
|
imei: number;
|
||||||
|
purchase_date: number;
|
||||||
|
retailer_name: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AverageProcessingTime {
|
||||||
|
imei: number;
|
||||||
|
invoice: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Usage {
|
||||||
|
imei: number;
|
||||||
|
invoice: number;
|
||||||
|
}
|
||||||
|
@ -1,35 +1,108 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { SbtPageHeader } from 'components/page-header';
|
import { SbtPageHeader } from 'components/page-header';
|
||||||
import { ReportDetailTable } from 'components/report-detail';
|
import { ReportOverViewTable } from 'components/report-detail';
|
||||||
import { Dayjs } from 'dayjs';
|
import { Dayjs } from 'dayjs';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
export interface ReportFormValues {
|
export interface ReportFormValues {
|
||||||
dateRange: [Dayjs, Dayjs];
|
dateRange: [Dayjs, Dayjs];
|
||||||
includeTest: string;
|
subsidiary: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
// const [form] = Form.useForm<ReportFormValues>();
|
||||||
|
// const [pagination, setPagination] = useState({
|
||||||
|
// page: 1,
|
||||||
|
// page_size: 10,
|
||||||
|
// });
|
||||||
|
// const [fromData, setFormData] = useState<{
|
||||||
|
// start_date: string;
|
||||||
|
// end_date: string;
|
||||||
|
// subsidiary: string;
|
||||||
|
// }>({
|
||||||
|
// start_date: '',
|
||||||
|
// end_date: '',
|
||||||
|
// subsidiary: '',
|
||||||
|
// });
|
||||||
|
// const { isLoading, data } = useOverViewReport({
|
||||||
|
// page: pagination.page,
|
||||||
|
// page_size: pagination.page_size,
|
||||||
|
// start_date: fromData.start_date,
|
||||||
|
// end_date: fromData.end_date,
|
||||||
|
// subsidiary: fromData.subsidiary,
|
||||||
|
// });
|
||||||
|
// const handleSubmit = (values: ReportFormValues) => {
|
||||||
|
// console.log('check values >>>', values);
|
||||||
|
// setFormData({
|
||||||
|
// start_date: values.dateRange[0].format('YYYY-MM-DDTHH:mm:ssZ'),
|
||||||
|
// end_date: values.dateRange[1].format('YYYY-MM-DDTHH:mm:ssZ'),
|
||||||
|
// subsidiary: values.subsidiary,
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SbtPageHeader
|
<SbtPageHeader
|
||||||
title={t`Dashboard`}
|
title={t`Dashboard`}
|
||||||
extra={
|
extra={
|
||||||
<Button
|
<>
|
||||||
size='large'
|
{/* <Button type='primary' icon={<DownloadOutlined />}>
|
||||||
type='primary'
|
Download
|
||||||
onClick={() => navigate('/reports')}
|
</Button> */}
|
||||||
// icon={<PlusOutlined />}
|
<Button type='primary' onClick={() => navigate('/reports')}>
|
||||||
>
|
{t`Go to Report page`}
|
||||||
{t`Go to Report page`}
|
</Button>
|
||||||
</Button>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{/* <Form
|
||||||
|
form={form}
|
||||||
|
style={{ display: 'flex', flexDirection: 'row', gap: 10 }}
|
||||||
|
onFinish={handleSubmit}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name='dateRange'
|
||||||
|
label={t`Date`}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: 'Please select a date range',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<DatePicker.RangePicker />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<ReportDetailTable />
|
<Form.Item
|
||||||
|
name='subsidiary'
|
||||||
|
label={t`Subsidiary`}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: 'Please select a subsidiary',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder='Select a subsidiary'
|
||||||
|
style={{ width: 200 }}
|
||||||
|
options={[
|
||||||
|
{ value: 'all', label: 'ALL' },
|
||||||
|
{ value: 'sesp', label: 'SESP' },
|
||||||
|
{ value: 'seau', label: 'SEAU' },
|
||||||
|
]}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type='primary' htmlType='submit'>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form> */}
|
||||||
|
<ReportOverViewTable />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -252,7 +252,12 @@ const ReportDetail = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SbtPageHeader
|
<SbtPageHeader
|
||||||
title={t`Report`}
|
title={
|
||||||
|
<Tooltip
|
||||||
|
title={id}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
>{t`Report ${id.slice(0, 16)}...`}</Tooltip>
|
||||||
|
}
|
||||||
extra={
|
extra={
|
||||||
<Button size='large' type='primary' icon={<DownloadOutlined />}>
|
<Button size='large' type='primary' icon={<DownloadOutlined />}>
|
||||||
{t`Download Report`}
|
{t`Download Report`}
|
||||||
@ -279,8 +284,8 @@ const ReportDetail = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
position: ['bottomLeft'],
|
|
||||||
}}
|
}}
|
||||||
|
scroll={{ x: 2000 }}
|
||||||
/>
|
/>
|
||||||
</ReportContainer>
|
</ReportContainer>
|
||||||
</>
|
</>
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { ReportListParams } from 'models';
|
import { ReportListParams } from 'models';
|
||||||
import { getReportDetailList, getReportList, makeReport } from 'request/report';
|
import {
|
||||||
|
getOverViewReport,
|
||||||
|
getReportDetailList,
|
||||||
|
getReportList,
|
||||||
|
makeReport,
|
||||||
|
} from 'request/report';
|
||||||
import {
|
import {
|
||||||
CustomUseMutationOptions,
|
CustomUseMutationOptions,
|
||||||
MakeReportParams,
|
MakeReportParams,
|
||||||
@ -36,3 +41,11 @@ export function useReportList(params?: ReportListParams, options?: any) {
|
|||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useOverViewReport(params?: ReportListParams, options?: any) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['overview-report', params],
|
||||||
|
queryFn: () => getOverViewReport(params),
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -1,43 +1,16 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import {
|
import { CustomUseMutationOptions, User } from 'models';
|
||||||
CustomUseMutationOptions,
|
|
||||||
CustomUseQueryOptions,
|
|
||||||
Pagination,
|
|
||||||
User,
|
|
||||||
} from 'models';
|
|
||||||
import {
|
import {
|
||||||
activateUser,
|
activateUser,
|
||||||
changePassword,
|
changePassword,
|
||||||
changePasswordForOther,
|
changePasswordForOther,
|
||||||
deactivateUser,
|
deactivateUser,
|
||||||
getSelf,
|
|
||||||
getSession,
|
|
||||||
getUsers,
|
|
||||||
GetUsersParams,
|
|
||||||
logIn,
|
logIn,
|
||||||
logOut,
|
logOut,
|
||||||
registerAccount,
|
registerAccount,
|
||||||
updateUser,
|
updateUser,
|
||||||
} from 'request';
|
} from 'request';
|
||||||
|
|
||||||
export function useSelf(options?: CustomUseQueryOptions<User>) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['user-self'],
|
|
||||||
queryFn: getSelf,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUserSession(
|
|
||||||
options?: CustomUseQueryOptions<Awaited<ReturnType<typeof getSession>>>,
|
|
||||||
) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['user-session'],
|
|
||||||
queryFn: getSession,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useLogIn(options?: CustomUseMutationOptions) {
|
export function useLogIn(options?: CustomUseMutationOptions) {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: logIn,
|
mutationFn: logIn,
|
||||||
@ -61,17 +34,6 @@ export function useRegister(options?: CustomUseMutationOptions) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUsers(
|
|
||||||
params: GetUsersParams,
|
|
||||||
options?: CustomUseQueryOptions<Pagination<User>>,
|
|
||||||
) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['users', params],
|
|
||||||
queryFn: () => getUsers(params),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUpdateUser(options?: CustomUseMutationOptions<User>) {
|
export function useUpdateUser(options?: CustomUseMutationOptions<User>) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import axios from 'axios';
|
import { i18n } from '@lingui/core';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { notification } from 'antd';
|
||||||
|
import axios, { AxiosError, HttpStatusCode } from 'axios';
|
||||||
import { getLocale } from 'i18n';
|
import { getLocale } from 'i18n';
|
||||||
|
import { queryClient } from 'queries';
|
||||||
|
import { ErrorData } from 'utils/error-handler';
|
||||||
|
|
||||||
const AXIOS_TIMEOUT_MS = 30 * 60 * 1000; // This config sastified long-live upload file request
|
const AXIOS_TIMEOUT_MS = 30 * 60 * 1000; // This config sastified long-live upload file request
|
||||||
const EXPIRED_PASSWORD_SIGNAL = 'expired_password';
|
const EXPIRED_PASSWORD_SIGNAL = 'expired_password';
|
||||||
@ -67,51 +72,51 @@ API.interceptors.request.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// interceptor to handle unauthenticated (401) or server error (502)
|
// interceptor to handle unauthenticated (401) or server error (502)
|
||||||
// API.interceptors.response.use(
|
API.interceptors.response.use(
|
||||||
// function (value) {
|
function (value) {
|
||||||
// return value;
|
return value;
|
||||||
// },
|
},
|
||||||
// function onError(error) {
|
function onError(error) {
|
||||||
// if (
|
if (
|
||||||
// // user is currently not in login page and any API return unauthenticated status
|
// user is currently not in login page and any API return unauthenticated status
|
||||||
// error instanceof AxiosError &&
|
error instanceof AxiosError &&
|
||||||
// error.response?.status === HttpStatusCode.Unauthorized &&
|
error.response?.status === HttpStatusCode.Unauthorized &&
|
||||||
// window.location.pathname !== '/auth/login'
|
window.location.pathname !== '/auth/login'
|
||||||
// ) {
|
) {
|
||||||
// const isPasswordExpired = (
|
const isPasswordExpired = (
|
||||||
// error as AxiosError<ErrorData>
|
error as AxiosError<ErrorData>
|
||||||
// ).response?.data.errors?.some((err) => {
|
).response?.data.errors?.some((err) => {
|
||||||
// return [err.code, err.message].includes(EXPIRED_PASSWORD_SIGNAL);
|
return [err.code, err.message].includes(EXPIRED_PASSWORD_SIGNAL);
|
||||||
// });
|
});
|
||||||
// const isPasswordExpiredOnGlobalState = queryClient.getQueryData<boolean>([
|
const isPasswordExpiredOnGlobalState = queryClient.getQueryData<boolean>([
|
||||||
// 'isPasswordExpired',
|
'isPasswordExpired',
|
||||||
// ]);
|
]);
|
||||||
|
|
||||||
// if (isPasswordExpired) {
|
if (isPasswordExpired) {
|
||||||
// if (!isPasswordExpiredOnGlobalState) {
|
if (!isPasswordExpiredOnGlobalState) {
|
||||||
// queryClient.setQueryData(['isPasswordExpired'], isPasswordExpired);
|
queryClient.setQueryData(['isPasswordExpired'], isPasswordExpired);
|
||||||
// notification.warning({
|
notification.warning({
|
||||||
// key: EXPIRED_PASSWORD_SIGNAL,
|
key: EXPIRED_PASSWORD_SIGNAL,
|
||||||
// message: i18n._(
|
message: i18n._(
|
||||||
// t`Your current password has expired. Please change your password to continue.`,
|
t`Your current password has expired. Please change your password to continue.`,
|
||||||
// ),
|
),
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// } else {
|
} else {
|
||||||
// localStorage.removeItem('sbt-token');
|
localStorage.removeItem('sbt-token');
|
||||||
// queryClient.setQueryData(['isAuthenticated'], false);
|
queryClient.setQueryData(['isAuthenticated'], false);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (
|
if (
|
||||||
// error instanceof AxiosError &&
|
error instanceof AxiosError &&
|
||||||
// [HttpStatusCode.ServiceUnavailable, HttpStatusCode.BadGateway].includes(
|
[HttpStatusCode.ServiceUnavailable, HttpStatusCode.BadGateway].includes(
|
||||||
// error.response?.status,
|
error.response?.status,
|
||||||
// )
|
)
|
||||||
// ) {
|
) {
|
||||||
// queryClient.setQueryData(['imperativeError'], error);
|
queryClient.setQueryData(['imperativeError'], error);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
// },
|
},
|
||||||
// );
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
MakeReportParams,
|
MakeReportParams,
|
||||||
MakeReportResponse,
|
MakeReportResponse,
|
||||||
|
OverViewDataResponse,
|
||||||
ReportDetailList,
|
ReportDetailList,
|
||||||
ReportDetailListParams,
|
ReportDetailListParams,
|
||||||
ReportListParams,
|
ReportListParams,
|
||||||
@ -56,3 +57,20 @@ export async function getReportList(params?: ReportListParams) {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getOverViewReport(params?: ReportListParams) {
|
||||||
|
try {
|
||||||
|
const response = await API.get<OverViewDataResponse>('/ctel/overview/', {
|
||||||
|
params: {
|
||||||
|
page: params?.page,
|
||||||
|
page_size: params?.page_size,
|
||||||
|
start_date: params?.start_date,
|
||||||
|
end_date: params?.end_date,
|
||||||
|
subsidiary: params?.subsidiary,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { DEFAULT_PAGE, DEFAULT_PAGE_SIZE } from 'consts';
|
import { User } from 'models';
|
||||||
import { Pagination, PaginationParams, User } from 'models';
|
|
||||||
import { API, PublicAPI } from './api';
|
import { API, PublicAPI } from './api';
|
||||||
|
|
||||||
export async function getSelf() {
|
export async function getSelf() {
|
||||||
@ -49,30 +48,6 @@ export async function registerAccount(payload: RegisterPayload) {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetUsersParams extends PaginationParams {
|
|
||||||
search?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getUsers({
|
|
||||||
page = DEFAULT_PAGE,
|
|
||||||
pageSize = DEFAULT_PAGE_SIZE,
|
|
||||||
search,
|
|
||||||
}: GetUsersParams) {
|
|
||||||
const urlSearchParams = new URLSearchParams({
|
|
||||||
page: String(page),
|
|
||||||
page_size: String(pageSize),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
urlSearchParams.append('search', search);
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await API.get<Pagination<User>>(
|
|
||||||
`/users?${urlSearchParams.toString()}`,
|
|
||||||
);
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateUser(
|
export async function updateUser(
|
||||||
partialUser: Omit<Partial<User>, 'id'> & { id: User['id'] },
|
partialUser: Omit<Partial<User>, 'id'> & { id: User['id'] },
|
||||||
) {
|
) {
|
||||||
|
@ -6,7 +6,7 @@ export function PublicRoute({ element }: { element: React.ReactNode }) {
|
|||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return <Navigate to='/reports' />;
|
return <Navigate to='/dashboard' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{element}</>;
|
return <>{element}</>;
|
||||||
|
Loading…
Reference in New Issue
Block a user