Merge pull request #17 from SDSRV-IDP/fix/fe-bugs

Fix: FE issues
This commit is contained in:
Đỗ Xuân Tân 2024-02-05 21:05:36 +07:00 committed by GitHub Enterprise
commit aa686b7367
12 changed files with 235 additions and 105 deletions

21
cope2n-fe/Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM node:21-alpine AS build
WORKDIR /app/
COPY --chown=node:node package*.json ./
RUN npm install -g npm@10.4.0 && npm install
COPY --chown=node:node . .
RUN npm run build
RUN npm cache clean --force
USER node
###################
# PRODUCTION
###################
FROM nginx:stable-alpine AS nginx
COPY --from=build /app/dist/ /usr/share/nginx/html/
COPY --from=build /app/run.sh /app/
COPY --from=build /app/nginx.conf /configs/
RUN chmod +x /app/run.sh
CMD ["/app/run.sh"]

35
cope2n-fe/nginx.conf Normal file
View File

@ -0,0 +1,35 @@
server {
# listen {{port}};
# listen [::]:{{port}};
# server_name localhost;
client_max_body_size 100M;
location ~ ^/api {
proxy_pass {{proxy_server}};
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
}
location /static/drf_spectacular_sidecar/ {
alias /backend-static/drf_spectacular_sidecar/;
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri /index.html;
}
location ~ ^/static/drf_spectacular_sidecar/swagger-ui-dist {
proxy_pass {{proxy_server}};
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

5
cope2n-fe/run.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/sh
# update port and BD proxy
sed "s#{{proxy_server}}#$VITE_PROXY#g" /configs/nginx.conf > /etc/nginx/conf.d/default.conf
# run up
nginx -g 'daemon off;'

View File

@ -30,15 +30,19 @@ const columns: TableColumnsType<DataType> = [
width: '100px', width: '100px',
render: (_, record) => { render: (_, record) => {
if (record.subSidiaries === '+') return ''; if (record.subSidiaries === '+') return '';
return record.subSidiaries; return String(record.subSidiaries).toUpperCase();
}, },
filters: [ filters: [
{ text: 'all', value: 'all' }, { text: 'ALL', value: 'ALL' },
{ text: 'sesp', value: 'sesp' }, { text: 'SEAU', value: 'SEAU' },
{ text: 'seau', value: 'seau' }, { text: 'SESP', value: 'SESP' },
{ text: 'SME', value: 'SME' },
{ text: 'SEPCO', value: 'SEPCO' },
{ text: 'TSE', value: 'TSE' },
{ text: 'SEIN', value: 'SEIN' },
], ],
filterMode: 'menu', filterMode: 'menu',
onFilter: (value: string, record) => record.subSidiaries.includes(value), onFilter: (value: string, record) => record.subSidiaries.includes(String(value).toUpperCase()),
}, },
{ {
title: 'OCR extraction date', title: 'OCR extraction date',
@ -216,23 +220,11 @@ const columns: TableColumnsType<DataType> = [
]; ];
interface ReportOverViewTableProps { interface ReportOverViewTableProps {
pagination: {
page: number;
page_size: number;
};
setPagination: React.Dispatch<
React.SetStateAction<{
page: number;
page_size: number;
}>
>;
isLoading: boolean; isLoading: boolean;
data: any; data: any;
} }
const ReportOverViewTable: React.FC<ReportOverViewTableProps> = ({ const ReportOverViewTable: React.FC<ReportOverViewTableProps> = ({
pagination,
setPagination,
isLoading, isLoading,
data, data,
}) => { }) => {
@ -270,20 +262,6 @@ const ReportOverViewTable: React.FC<ReportOverViewTableProps> = ({
bordered bordered
size='small' size='small'
scroll={{ x: 2000 }} scroll={{ x: 2000 }}
pagination={{
current: pagination.page,
pageSize: 10,
total: dataSubsRows?.length,
// showTotal: (total, range) =>
// `${range[0]}-${range[1]} of ${total} items`,
onChange: (page, pageSize) => {
setPagination({
page,
page_size: pageSize || 10,
});
},
showSizeChanger: false,
}}
/> />
</div> </div>
); );

View File

@ -37,15 +37,42 @@ const ReportTable: React.FC = () => {
title: 'ID', title: 'ID',
dataIndex: 'ID', dataIndex: 'ID',
key: 'ID', key: 'ID',
sorter: (a, b) => a.ID - b.ID,
}, },
{ {
title: 'Created Date', title: 'Report Date',
dataIndex: 'Created Date', dataIndex: 'Created Date',
key: 'Created Date', key: 'Created Date',
render: (_, record) => { render: (_, record) => {
return <span>{record['Created Date'].toString().split('T')[0]}</span>; return <span>{record['Created Date'].toString().split('T')[0]}</span>;
}, },
width: 110,
},
{
title: 'Start Date',
dataIndex: 'Start Date',
key: 'Start Date',
render: (_, record) => {
return <span>{record['Start Date'].toString().split('T')[0]}</span>;
},
width: 110,
},
{
title: 'End Date',
dataIndex: 'End Date',
key: 'End Date',
render: (_, record) => {
return <span>{record['End Date'].toString().split('T')[0]}</span>;
},
width: 110,
},
{
title: 'Subsidiary',
dataIndex: 'Subsidiary',
key: 'Subsidiary',
render: (_, record) => {
return <span>{String(record['Subsidiary']).toUpperCase()}</span>;
},
width: 110,
}, },
{ {
title: 'No. Requests', title: 'No. Requests',
@ -149,7 +176,7 @@ const ReportTable: React.FC = () => {
title: 'Actions', title: 'Actions',
dataIndex: 'actions', dataIndex: 'actions',
key: 'actions', key: 'actions',
width: 200, width: 240,
render: (_, record) => { render: (_, record) => {
return ( return (
<div style={{ flexDirection: 'row' }}> <div style={{ flexDirection: 'row' }}>
@ -159,7 +186,7 @@ const ReportTable: React.FC = () => {
}} }}
style={{ marginRight: 10 }} style={{ marginRight: 10 }}
> >
Detail Details
</Button> </Button>
<Button onClick={() => handleDownloadReport(record.report_id)}> <Button onClick={() => handleDownloadReport(record.report_id)}>
Download Download

View File

@ -53,6 +53,11 @@ export type ReportListParams = {
subsidiary?: string; subsidiary?: string;
}; };
export type DashboardOverviewParams = {
duration?: string;
subsidiary?: string;
};
export interface MakeReportResponse { export interface MakeReportResponse {
report_id: string; report_id: string;
} }

View File

@ -1,112 +1,120 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button, DatePicker, Form, Select } from 'antd'; import { Button, Form, Select } from 'antd';
import { SbtPageHeader } from 'components/page-header'; import { SbtPageHeader } from 'components/page-header';
import { ReportOverViewTable } from 'components/report-detail'; import { ReportOverViewTable } from 'components/report-detail';
import { Dayjs } from 'dayjs';
import { useOverViewReport } from 'queries/report'; import { useOverViewReport } from 'queries/report';
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { DownloadOutlined } from '@ant-design/icons';
import { downloadDashboardReport } from 'request/report';
export interface ReportFormValues { export interface ReportFormValues {
dateRange: [Dayjs, Dayjs]; duration: string;
subsidiary: string; subsidiary: string;
} }
const Dashboard = () => { const Dashboard = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [duration, setDuration] = useState<string>('30d');
const [subsidiary, setSubsidiary] = useState<string>('ALL');
const [form] = Form.useForm<ReportFormValues>(); 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({ const { isLoading, data } = useOverViewReport({
start_date: fromData.start_date, duration: duration,
end_date: fromData.end_date, subsidiary: subsidiary,
subsidiary: fromData.subsidiary,
page: pagination.page,
page_size: 30,
}); });
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,
});
};
const handleDownloadReport = async () => {
console.log('duration >>>', duration);
console.log('subsidiary >>>', subsidiary);
const {file, filename} = await downloadDashboardReport(duration, subsidiary);
const anchorElement = document.createElement('a');
anchorElement.href = URL.createObjectURL(file);
anchorElement.download = filename;
document.body.appendChild(anchorElement);
anchorElement.click();
// Clean up
document.body.removeChild(anchorElement);
URL.revokeObjectURL(anchorElement.href);
};
return ( return (
<> <>
<SbtPageHeader <SbtPageHeader
title={t`Dashboard`} title={t`Dashboard`}
extra={ extra={
<> <>
{/* <Button type='primary' icon={<DownloadOutlined />}> <Button type='primary' size='large' icon={<DownloadOutlined />}
Download onClick={() => handleDownloadReport()}
</Button> */} >
{/* <Button type='primary' onClick={() => navigate('/reports')}> {t`Download`}
{t`Go to Report page`} </Button>
</Button> */} <Button type='primary' size='large' onClick={() => navigate('/reports')}>
{t`Go to Reports`}
</Button>
</> </>
} }
/> />
<Form <Form
form={form} form={form}
style={{ display: 'flex', flexDirection: 'row', gap: 10 }} style={{ display: 'flex', flexDirection: 'row', gap: 10 }}
onFinish={handleSubmit}
> >
<Form.Item <Form.Item
name='dateRange' label={t`Duration`}
label={t`Date`}
rules={[ rules={[
{ {
required: true, required: true,
message: 'Please select a date range', message: 'Please select a duration',
}, },
]} ]}
required
> >
<DatePicker.RangePicker /> <Select
placeholder='Select a date range'
style={{ width: 200 }}
options={[
{ value: '30d', label: 'Last 30 days' },
{ value: '7d', label: 'Last 7 days' },
]}
value={duration}
onChange={(value) => {
setDuration(value);
}}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name='subsidiary' name='subsidiary'
label={t`Subsidiary`} label={t`Subsidiary`}
rules={[ required
{
required: true,
message: 'Please select a subsidiary',
},
]}
> >
<Select <Select
placeholder='Select a subsidiary' placeholder='Select a subsidiary'
style={{ width: 200 }} style={{ width: 200 }}
options={[ options={[
{ value: 'all', label: 'ALL' }, { value: 'ALL', label: 'ALL' },
{ value: 'sesp', label: 'SESP' }, { value: 'SEAU', label: 'SEAU' },
{ value: 'seau', label: 'SEAU' }, { value: 'SESP', label: 'SESP' },
{ value: 'SME', label: 'SME' },
{ value: 'SEPCO', label: 'SEPCO' },
{ value: 'TSE', label: 'TSE' },
{ value: 'SEIN', label: 'SEIN' },
]} ]}
allowClear defaultValue='ALL'
value={subsidiary}
onChange={(value) => {
setSubsidiary(value);
}}
/> />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type='primary' htmlType='submit' style={{ height: 38 }}> <Button type='primary' style={{ height: 38 }}
Submit >
View
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>
<ReportOverViewTable <ReportOverViewTable
pagination={pagination}
setPagination={setPagination}
isLoading={isLoading} isLoading={isLoading}
data={data} data={data}
/> />

View File

@ -64,6 +64,9 @@ const InferencePage = () => {
<SbtPageHeader <SbtPageHeader
title={t`Inference`} title={t`Inference`}
/> />
<p>
{t`Upload files to process. The requests here will not be used in accuracy or payment calculations.`}
</p>
<div style={{ <div style={{
paddingTop: "0.5rem" paddingTop: "0.5rem"
}}> }}>

View File

@ -105,9 +105,13 @@ const ReportsPage = () => {
placeholder='Select a subsidiary' placeholder='Select a subsidiary'
style={{ width: 200 }} style={{ width: 200 }}
options={[ options={[
{ value: 'all', label: 'ALL' }, { value: 'ALL', label: 'ALL' },
{ value: 'sesp', label: 'SESP' }, { value: 'SEAU', label: 'SEAU' },
{ value: 'seau', label: 'SEAU' }, { value: 'SESP', label: 'SESP' },
{ value: 'SME', label: 'SME' },
{ value: 'SEPCO', label: 'SEPCO' },
{ value: 'TSE', label: 'TSE' },
{ value: 'SEIN', label: 'SEIN' },
]} ]}
/> />
</Form.Item> </Form.Item>

View File

@ -1,4 +1,4 @@
import { DownloadOutlined } from '@ant-design/icons'; import { DownloadOutlined, ArrowLeftOutlined } from '@ant-design/icons';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
Button, Button,
@ -271,14 +271,32 @@ const ReportDetail = () => {
URL.revokeObjectURL(anchorElement.href); URL.revokeObjectURL(anchorElement.href);
}; };
const handleBack = () => {
window.history.back();
};
return ( return (
<> <>
<SbtPageHeader <SbtPageHeader
title={ title={
<Tooltip <>
title={id} <Tooltip title={t`Back`}>
style={{ cursor: 'pointer' }} <Button
>{t`Report ${id.slice(0, 16)}...`}</Tooltip> size='middle'
type='default'
icon={<ArrowLeftOutlined />}
onClick={() => handleBack()}
style={{
marginRight: 10,
lineHeight: '1.8',
height: 38
}}
>
{t`Back`}
</Button>
</Tooltip>
{t`Report Details`}
</>
} }
extra={ extra={
<Button <Button
@ -298,7 +316,6 @@ const ReportDetail = () => {
{report_data?.metadata?.subsidiary} {report_data?.metadata?.subsidiary}
</span> </span>
</Typography.Title> </Typography.Title>
<Typography.Title level={5}> <Typography.Title level={5}>
Start date:{' '} Start date:{' '}
<span style={{ fontWeight: '400' }}> <span style={{ fontWeight: '400' }}>

View File

@ -1,5 +1,5 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { ReportListParams } from 'models'; import { ReportListParams, DashboardOverviewParams } from 'models';
import { import {
getOverViewReport, getOverViewReport,
getReportDetailList, getReportDetailList,
@ -42,7 +42,7 @@ export function useReportList(params?: ReportListParams, options?: any) {
}); });
} }
export function useOverViewReport(params?: ReportListParams, options?: any) { export function useOverViewReport(params?: DashboardOverviewParams, options?: any) {
return useQuery({ return useQuery({
queryKey: ['overview-report', params], queryKey: ['overview-report', params],
queryFn: () => getOverViewReport(params), queryFn: () => getOverViewReport(params),

View File

@ -7,6 +7,7 @@ import {
ReportDetailListParams, ReportDetailListParams,
ReportListParams, ReportListParams,
ReportListType, ReportListType,
DashboardOverviewParams,
} from 'models'; } from 'models';
import { API } from './api'; import { API } from './api';
@ -68,14 +69,11 @@ export async function getReportList(params?: ReportListParams) {
} }
} }
export async function getOverViewReport(params?: ReportListParams) { export async function getOverViewReport(params?: DashboardOverviewParams) {
try { try {
const response = await API.get<OverViewDataResponse>('/ctel/overview/', { const response = await API.get<OverViewDataResponse>('/ctel/overview/', {
params: { params: {
page: params?.page, duration: params?.duration,
page_size: params?.page_size,
start_date: params?.start_date,
end_date: params?.end_date,
subsidiary: params?.subsidiary, subsidiary: params?.subsidiary,
}, },
}); });
@ -104,6 +102,35 @@ export async function downloadReport(report_id: string) {
const file = new Blob([response.data], { const file = new Blob([response.data], {
type: 'application/vnd.ms-excel', type: 'application/vnd.ms-excel',
}); });
return {
file: file,
filename: filename,
}
} catch (error) {
notification.error({
message: `${error?.message}`,
});
console.log(error);
}
}
export async function downloadDashboardReport(duration='30d', subsidiary='ALL') {
try {
const response = await API.get(`/ctel/overview_download_file/?duration=${duration}&subsidiary=${subsidiary}`, {
responseType: 'blob', // Important
});
let filename = "report.xlsx";
try {
let basename = response.headers['content-disposition'].split('filename=')[1].split('.')[0];
let extension = response.headers['content-disposition'].split('.')[1].split(';')[0];
filename = `${basename}.${extension}`
} catch(err) {
console.log(err);
}
const file = new Blob([response.data], {
type: 'application/vnd.ms-excel',
});
// const fileURL = URL.createObjectURL(file); // const fileURL = URL.createObjectURL(file);
// window.open(fileURL); // window.open(fileURL);
return { return {