feat: add table column diy

This commit is contained in:
LittleBoy 2024-08-08 20:24:10 +08:00
parent 66330f4913
commit 9c6fbd839d

View File

@ -1,14 +1,16 @@
import {Space, Table, Typography} from "@douyinfe/semi-ui"; import {Button, Checkbox, CheckboxGroup, Space, Table, Typography} from "@douyinfe/semi-ui";
import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table"; import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table";
import React, {useMemo, useState} from "react"; import React, {ReactNode, useMemo, useState} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {IconCheckCircleStroked} from "@douyinfe/semi-icons"; import {IconCheckCircleStroked, IconSetting} from "@douyinfe/semi-icons";
import MoneyFormat from "@/components/money-format.tsx"; import MoneyFormat from "@/components/money-format.tsx";
import {Card} from "@/components/card"; import {Card} from "@/components/card";
import './bill.less' import './bill.less'
import {BillStatus} from "@/service/types.ts"; import {BillStatus} from "@/service/types.ts";
import {useSetState} from "ahooks";
import {clone} from "lodash";
type BillListProps = { type BillListProps = {
type: 'query' | 'reconciliation'; type: 'query' | 'reconciliation';
@ -16,25 +18,34 @@ type BillListProps = {
operationRenderWidth?: number; operationRenderWidth?: number;
onRowSelection?: (selectedRowKeys: (string | number)[]) => void; onRowSelection?: (selectedRowKeys: (string | number)[]) => void;
source?: RecordList<BillModel>; source?: RecordList<BillModel>;
onPageChange: (pageIndex:number) => void; onPageChange: (pageIndex: number) => void;
tableFooter?: React.ReactNode; tableFooter?: React.ReactNode;
loading?: boolean; loading?: boolean;
beforeTotalAmount?: React.ReactNode; beforeTotalAmount?: React.ReactNode;
} }
const CheckNumberCorrect = ({origin,confirmed}:{origin: string,confirmed?:string}) => { const CheckNumberCorrect = ({origin, confirmed}: { origin: string, confirmed?: string }) => {
if(origin == confirmed && origin){ if (origin == confirmed && origin) {
return (<Space style={{marginTop:2,color:'green'}}><span>{origin}</span><IconCheckCircleStroked /></Space>) return (<Space style={{marginTop: 2, color: 'green'}}><span>{origin}</span><IconCheckCircleStroked/></Space>)
} }
return <div style={{lineHeight:1}}> return <div style={{lineHeight: 1}}>
<div style={confirmed?{color:'red'}:{}}>{origin?.length ?origin: 'N/A'}</div> <div style={confirmed ? {color: 'red'} : {}}>{origin?.length ? origin : 'N/A'}</div>
{confirmed&&<Space style={{marginTop:2,color:'green'}}><span>{confirmed}</span><IconCheckCircleStroked /></Space>} {confirmed &&
<Space style={{marginTop: 2, color: 'green'}}><span>{confirmed}</span><IconCheckCircleStroked/></Space>}
</div> </div>
} }
export const BillList: React.FC<BillListProps> = (props) => { export const BillList: React.FC<BillListProps> = (props) => {
const {t, i18n} = useTranslation() const {t, i18n} = useTranslation()
const [currentTotalAmount,setCurrentTotalAmount] = useState(0) const [currentTotalAmount, setCurrentTotalAmount] = useState(0)
const [state, setState] = useSetState<{
showColumnsConfig?: boolean;
showCols: string[]
}>({
showCols: [
"id", "merchant_ref", "student_number", "application_number", "initiated_paid_at", "delivered_at", "paid_at", "student_english_name", "student_email", "programme_chinese_name",
"intake_year", "detail", "_detail", "amount", "pay_amount", "actual_payment_amount", "pay_method", "status", "apply_status"
]
})
const billStatusText = (billStatus: string) => { const billStatusText = (billStatus: string) => {
switch (billStatus) { switch (billStatus) {
@ -51,7 +62,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
return billStatus return billStatus
} }
} }
const applyStatusText = (status:string) => { const applyStatusText = (status: string) => {
switch (status) { switch (status) {
case 'UNCHECKED': case 'UNCHECKED':
return t('bill.reconciliation_status_pending') return t('bill.reconciliation_status_pending')
@ -62,7 +73,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
} }
} }
const columns = useMemo<ColumnProps<BillModel>[]>(() => { const allCols = useMemo<ColumnProps<BillModel>[]>(() => {
const cols: ColumnProps<BillModel>[] = [ const cols: ColumnProps<BillModel>[] = [
{ {
title: '#ID', title: '#ID',
@ -79,38 +90,44 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: t('base.student_number'), title: t('base.student_number'),
dataIndex: 'student_number', dataIndex: 'student_number',
width: 150, width: 150,
render: (value,record) => (<CheckNumberCorrect origin={value} confirmed={record.student_number_confirm || 'test-confirm'} />) render: (value, record) => (
<CheckNumberCorrect origin={value} confirmed={record.student_number_confirm || 'test-confirm'}/>)
}, },
{ {
title: t('base.bill_number'), title: t('base.bill_number'),
dataIndex: 'application_number', dataIndex: 'application_number',
width: 150, width: 150,
render: (value,record) => (<CheckNumberCorrect origin={value} confirmed={value||record.application_number_confirm} />) render: (value, record) => (
<CheckNumberCorrect origin={value} confirmed={value || record.application_number_confirm}/>)
}, },
{ {
title: t('bill.title_initiated_paid_at'), title: <div className="table-header-title">{t('bill.title_initiated_paid_at')}
<div className="tips">(PPS Input Date)</div>
</div>,
dataIndex: 'initiated_paid_at', dataIndex: 'initiated_paid_at',
width: 180, width: 180,
render: (value) => value?.length ?value: 'N/A' render: (value) => value?.length ? value : 'N/A'
}, },
{ {
title: t('bill.title_delivered_at'), title: <div className="table-header-title">{t('bill.title_delivered_at')}
<div className="tips">(PPS Statement Date)</div>
</div>,
dataIndex: 'delivered_at', dataIndex: 'delivered_at',
width: 180, width: 180,
render: (value) => value?.length ?value: 'N/A' render: (value) => value?.length ? value : 'N/A'
}, },
{ {
title: t('bill.title_paid_at'), title: t('bill.title_paid_at'),
dataIndex: 'paid_at', dataIndex: 'paid_at',
width: 180, width: 180,
render: (value) => value?.length ?value: 'N/A' render: (value) => value?.length ? value : 'N/A'
},
{
title: t('bill.title_create_at'),
dataIndex: 'create_at',
width: 180,
render: (value) => value?.length ?value: 'N/A'
}, },
// {
// title: t('bill.title_create_at'),
// dataIndex: 'create_at',
// width: 180,
// render: (value) => value?.length ?value: 'N/A'
// },
{ {
title: t('bill.title_student_name'), title: t('bill.title_student_name'),
dataIndex: 'student_english_name', dataIndex: 'student_english_name',
@ -121,7 +138,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: 'Email', title: 'Email',
dataIndex: 'student_email', dataIndex: 'student_email',
width: 200, width: 200,
render: (value) => value?.length ?value: 'N/A' render: (value) => value?.length ? value : 'N/A'
// render: (_, record) => (<div>{record.student_english_name}<br/>{record.student_chinese_name}</div>) // render: (_, record) => (<div>{record.student_english_name}<br/>{record.student_chinese_name}</div>)
}, },
{ {
@ -132,13 +149,15 @@ export const BillList: React.FC<BillListProps> = (props) => {
{ {
title: t('bill.title_department'), title: t('bill.title_department'),
width: 200, width: 200,
dataIndex: i18n.language == 'en-US' ? 'department_english_name' : 'department_chinese_name', dataIndex: '',
render: (_, record) => (i18n.language == 'en-US' ? record.department_english_name : record.department_chinese_name),
}, },
{ {
title: t('bill.title_year'), title: t('bill.title_year'),
dataIndex: 'intake_year', dataIndex: 'intake_year',
width: 120, width: 120,
render: (_, record) => record.intake_year?(<div>{record.intake_year}/{String(record.intake_semester).length == 1 ? '0':''}{record.intake_semester}</div>):"N/A" render: (_, record) => record.intake_year ? (
<div>{record.intake_year}/{String(record.intake_semester).length == 1 ? '0' : ''}{record.intake_semester}</div>) : "N/A"
}, },
// { // {
// title: t('bill.title_semester'), // title: t('bill.title_semester'),
@ -150,8 +169,15 @@ export const BillList: React.FC<BillListProps> = (props) => {
dataIndex: 'detail', dataIndex: 'detail',
ellipsis: {showTitle: true}, ellipsis: {showTitle: true},
width: 220, width: 220,
render: (_, record) => (<div style={{fontSize: 13, lineHeight: 1.2,wordBreak:'break-all',maxWidth:'100%',whiteSpace:'normal'}}> render: (_, record) => (<div style={{
{record.details.map((it, idx) => (<div key={idx}>{it.bill_type}: <MoneyFormat money={it.amount}/></div>))} fontSize: 13,
lineHeight: 1.2,
wordBreak: 'break-all',
maxWidth: '100%',
whiteSpace: 'normal'
}}>
{record.details.map((it, idx) => (
<div key={idx}>{it.bill_type}: <MoneyFormat money={it.amount}/></div>))}
</div>), </div>),
}, },
{ {
@ -159,8 +185,15 @@ export const BillList: React.FC<BillListProps> = (props) => {
dataIndex: '_detail', dataIndex: '_detail',
ellipsis: {showTitle: true}, ellipsis: {showTitle: true},
width: 220, width: 220,
render: (_, record) => (<div style={{fontSize: 13, lineHeight: 1.2,wordBreak:'break-all',maxWidth:'100%',whiteSpace:'normal'}}> render: (_, record) => (<div style={{
{record.details.filter(s=>s.confirm_status == 'CONFIRMED').map((it) => (<div key={it.id}>{it.confirm_type}: <MoneyFormat money={it.amount}/></div>))} fontSize: 13,
lineHeight: 1.2,
wordBreak: 'break-all',
maxWidth: '100%',
whiteSpace: 'normal'
}}>
{record.details.filter(s => s.confirm_status == 'CONFIRMED').map((it) => (
<div key={it.id}>{it.confirm_type}: <MoneyFormat money={it.amount}/></div>))}
</div>), </div>),
}, },
{ {
@ -190,13 +223,13 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: t('bill.title_actual_payment_amount'), title: t('bill.title_actual_payment_amount'),
dataIndex: 'actual_payment_amount', dataIndex: 'actual_payment_amount',
width: 150, width: 150,
render: (_,record) => (<MoneyFormat money={_} currency={record.currency}/>), render: (_, record) => (<MoneyFormat money={_} currency={record.currency}/>),
}, },
{ {
title: t('bill.title_pay_method'), title: t('bill.title_pay_method'),
dataIndex: 'pay_method', dataIndex: 'pay_method',
width: 130, width: 130,
render: (_, {payment_method, payment_channel}) => (payment_channel?( render: (_, {payment_method, payment_channel}) => (payment_channel ? (
<div> <div>
{payment_channel} {payment_channel}
{payment_method && payment_method.length > 0 && <div> {payment_method && payment_method.length > 0 && <div>
@ -205,7 +238,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
</Typography.Text> </Typography.Text>
</div>} </div>}
</div> </div>
):'N/A'), ) : 'N/A'),
}, },
{ {
title: t('bill.title_bill_status'), title: t('bill.title_bill_status'),
@ -222,35 +255,47 @@ export const BillList: React.FC<BillListProps> = (props) => {
render: value => applyStatusText(value), render: value => applyStatusText(value),
}) })
} }
return cols;
}, [props.type, i18n.language])
const columns = useMemo<ColumnProps<BillModel>[]>(() => {
const _cols = clone(allCols);
const cols = state.showCols.length == 0 ? _cols : _cols.filter((it: ColumnProps<BillModel>) => !it.dataIndex || state.showCols.includes(it.dataIndex))
if (props.operationRender) { if (props.operationRender) {
cols.push({ cols.push({
title: t('bill.title_operate'), title: t('bill.title_operate'),
dataIndex: 'operate', dataIndex: 'operate',
fixed: 'right', fixed: 'right',
width: props.operationRenderWidth || (props.type == 'reconciliation'?120:220), width: props.operationRenderWidth || (props.type == 'reconciliation' ? 120 : 220),
render: (_, record) => props.operationRender?.(record), render: (_, record) => props.operationRender?.(record),
}) })
} }
return cols; return cols;
}, [props.operationRender, props.type, i18n.language]); }, [props.operationRender, props.type, i18n.language, allCols, state.showCols]);
const isExpired = (bill: BillModel) => { const isExpired = (bill: BillModel) => {
return bill.status == BillStatus.PENDING && dayjs(bill.expiration_time).isBefore(Date.now()) return bill.status == BillStatus.PENDING && dayjs(bill.expiration_time).isBefore(Date.now())
} }
const currentList = useMemo(()=>{
const currentList = useMemo(() => {
const originList = props.source?.list || []; const originList = props.source?.list || [];
originList.forEach(s => { originList.forEach(s => {
if(isExpired(s)){ if (isExpired(s)) {
s.status = BillStatus.EXPIRED; s.status = BillStatus.EXPIRED;
} }
}) })
const _total = originList.map(s=>Number(s.amount)).reduce((s, c) => (s + c), 0) const _total = originList.map(s => Number(s.amount)).reduce((s, c) => (s + c), 0)
setCurrentTotalAmount(_total) setCurrentTotalAmount(_total)
return originList; return originList;
},[props.source]) }, [props.source])
return <Card return <Card
title={t('bill.title_bill_list')} title={<Space>
<span>{t('bill.title_bill_list')}</span>
<span className={'cursor-pointer'} onClick={() => setState({showColumnsConfig: true})}>
<IconSetting size={'small'}/>
</span>
</Space>}
headerRight={<Space spacing={20}> headerRight={<Space spacing={20}>
{props.beforeTotalAmount} {props.beforeTotalAmount}
<div className="bill-info"> <div className="bill-info">
@ -265,6 +310,29 @@ export const BillList: React.FC<BillListProps> = (props) => {
</div> </div>
</Space>} </Space>}
> >
{state.showColumnsConfig && <div style={{
marginBottom: 20,
padding: 20,
backgroundColor: '#fafafa',
borderRadius: 5,
border: 'solid 1px #f0f0f0'
}}>
<div className="table-column-config">
<CheckboxGroup direction="horizontal" defaultValue={state.showCols}
onChange={showCols => setState({showCols})}>
{allCols.map((it) => {
return (<Checkbox value={it.dataIndex}><span>{it.title as ReactNode}</span></Checkbox>)
})}
</CheckboxGroup>
</div>
<div className="table-column-action" style={{marginTop: 20}}>
<Space>
<Button>{t('base.cancel')}</Button>
<Button theme={'solid'}>{t('base.confirm')}</Button>
</Space>
</div>
</div>}
<div className="bill-list-table"> <div className="bill-list-table">
<Table<BillModel> <Table<BillModel>
bordered bordered
@ -279,7 +347,8 @@ export const BillList: React.FC<BillListProps> = (props) => {
formatPageText: (params) => ( formatPageText: (params) => (
<div className="bill-list-pagination"> <div className="bill-list-pagination">
{props.tableFooter} {props.tableFooter}
{props.source && props.source.pagination.recordTotal > 0 && <span>{t('page.record-show',params)}</span>} {props.source && props.source.pagination.recordTotal > 0 &&
<span>{t('page.record-show', params)}</span>}
</div> </div>
) )
}} }}