280 lines
8.1 KiB
TypeScript
280 lines
8.1 KiB
TypeScript
import {Button, Col, Divider, Form, InputNumber, Modal, Row, Select, Space} from "@douyinfe/semi-ui";
|
|
import {IconAlertCircle} from "@douyinfe/semi-icons";
|
|
import React from "react";
|
|
import {useTranslation} from "react-i18next";
|
|
import {useSetState} from "ahooks";
|
|
|
|
import {useBillTypes} from "@/hooks/useBillTypes.ts";
|
|
import {usePaymentChannels} from "@/hooks/usePaymentChannels.ts";
|
|
import {addBillRecord} from "@/service/api/bill.ts"
|
|
import dayjs from "dayjs";
|
|
|
|
type BillPaidModalProps = {
|
|
onConfirm: () => void
|
|
onCancel?: () => void
|
|
}
|
|
type BillTypeListProps = {
|
|
onClose?: (refresh?: boolean) => void;
|
|
onChange?: (confirms: ConfirmedBillDetail[]) => void;
|
|
}
|
|
|
|
export const BillTypeList: React.FC<BillTypeListProps> = (props) => {
|
|
const {t} = useTranslation()
|
|
const [state, setState] = useSetState<{
|
|
confirmed: ConfirmedBillDetail[]
|
|
}>({
|
|
confirmed: [{bill_type: '', amount: 0}]
|
|
})
|
|
const BillTypes = useBillTypes()
|
|
|
|
const onChange = (value: string, index: number, type: 'type' | 'amount') => {
|
|
if (state.confirmed.length <= index || !value) return;
|
|
const confirmed = [...state.confirmed]
|
|
if (type == 'type') {
|
|
confirmed[index].bill_type = value
|
|
} else {
|
|
confirmed[index].amount = Number(value)
|
|
}
|
|
|
|
setState({confirmed})
|
|
props.onChange?.(confirmed)
|
|
}
|
|
|
|
const addOrRemove = (index: number) => {
|
|
// 不允许删除最后一个
|
|
if (index > -1 && state.confirmed.length <= 1) return;
|
|
const confirmed = [...state.confirmed, ...(index == -1 ? [{bill_type: '', amount: 0}] : [])]
|
|
if (index > -1) confirmed.splice(index, 1)
|
|
setState({confirmed})
|
|
props.onChange?.(confirmed)
|
|
}
|
|
|
|
|
|
return (<div className={'bill-type-list'} style={{marginTop: 10}}>
|
|
<Divider>{t('bill.title_bill_detail')}</Divider>
|
|
{
|
|
state.confirmed.map((item, index) => (
|
|
<div key={index} className="confirm-item-btn align-center space-between" style={{marginTop: 20}}>
|
|
<Select
|
|
value={item.bill_type}
|
|
style={{width: 240}}
|
|
onChange={v => onChange(String(v), index, 'type')}
|
|
placeholder={t('base.please_select_bill_type')}>
|
|
{
|
|
BillTypes.map((it, idx) => (
|
|
<Select.Option key={idx} value={it.label}>{it.label}</Select.Option>))
|
|
}
|
|
</Select>
|
|
|
|
<Space spacing={10}>
|
|
<InputNumber
|
|
hideButtons precision={2} value={item.amount} type={'number'}
|
|
onChange={v => onChange(String(v), index, 'amount')} style={{width: 140}}/>
|
|
<Button
|
|
disabled={state.confirmed.length <= 1} onClick={() => addOrRemove(index)}
|
|
theme={'solid'} type={'secondary'}>{t('base.remove')}</Button>
|
|
</Space>
|
|
</div>
|
|
))
|
|
}
|
|
<div style={{marginTop: 10, marginBottom: 20}}>
|
|
<Button onClick={() => addOrRemove(-1)}>{t('base.add')}</Button>
|
|
</div>
|
|
</div>)
|
|
}
|
|
|
|
export const AddBillModal: React.FC<BillPaidModalProps> = (props) => {
|
|
const {t} = useTranslation()
|
|
const {paymentChannelList, paymentMethodList} = usePaymentChannels();
|
|
|
|
const [state, setState] = useSetState<{
|
|
loading?: boolean;
|
|
open?: boolean;
|
|
details: ConfirmedBillDetail[];
|
|
errorMessage?: string;
|
|
}>({
|
|
details: []
|
|
})
|
|
|
|
|
|
const onSubmit = (values: CreateBillRecordModel) => {
|
|
if (state.details.length == 0) {
|
|
setState({errorMessage: t('base.validate.error_details_message')})
|
|
return;
|
|
}
|
|
for (const it of state.details) {
|
|
if (!it.bill_type || it.amount <= 0) {
|
|
setState({errorMessage: t('base.validate.error_details_message')})
|
|
return;
|
|
}
|
|
}
|
|
setState({
|
|
loading: true, errorMessage: undefined
|
|
})
|
|
values.details = state.details
|
|
values.check_student = true;
|
|
values.initiated_paid_date = dayjs(values.initiated_paid_date).format("YYYY-MM-DD")
|
|
values.paid_date = dayjs(values.paid_date).format("YYYY-MM-DD")
|
|
values.delivered_date = dayjs(values.delivered_date).format("YYYY-MM-DD")
|
|
addBillRecord(values).then(()=>{
|
|
setState({open:false})
|
|
props.onConfirm()
|
|
}).catch(e => {
|
|
setState({errorMessage: e.message})
|
|
}).finally(() => {
|
|
setState({
|
|
loading: false
|
|
})
|
|
})
|
|
//
|
|
}
|
|
const handleClose = ()=>{
|
|
setState({
|
|
loading: false,
|
|
open:false,
|
|
errorMessage:undefined
|
|
})
|
|
}
|
|
return (<>
|
|
<Button onClick={() => setState({open: true})} theme={'solid'}>{t('bill.add_bill_record')}</Button>
|
|
<Modal
|
|
title={t('bill.add_bill_record')}
|
|
visible={state.open}
|
|
closeOnEsc={true}
|
|
onCancel={handleClose}
|
|
footer={null}
|
|
width={600}
|
|
maskClosable={false}
|
|
>
|
|
|
|
<Form<CreateBillRecordModel> onSubmit={onSubmit} initValues={{
|
|
merchant_ref: '',
|
|
application_number: '',
|
|
student_email: '',
|
|
paid_area: '',
|
|
initiated_paid_date: '',
|
|
paid_date: '',
|
|
delivered_date: '',
|
|
payment_method: '',
|
|
payment_channel: '',
|
|
check_student: true,
|
|
details: []
|
|
}}>
|
|
<Row gutter={20}>
|
|
<Col span={12}>
|
|
<Form.Input
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
]}
|
|
showClear field="merchant_ref" label="Merchant Ref"
|
|
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Input
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
]}
|
|
showClear field="application_number" label={t('bill.bill_number')}
|
|
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={20}>
|
|
<Col span={12}>
|
|
<Form.Input
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
{type: 'email', message: t('base.validate.email')}
|
|
]}
|
|
type={'email'}
|
|
showClear field="student_email" label="Email"
|
|
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Input
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
]}
|
|
showClear field="paid_area" label={t('bill.create.pay_area')}
|
|
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={20}>
|
|
<Col span={12}>
|
|
<Form.Select
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
]}
|
|
optionList={paymentChannelList}
|
|
allowCreate filter showClear field="payment_channel" label={t('bill.title_pay_channel')}
|
|
placeholder={t('base.please_select')} style={{width: '100%'}}/>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Select
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
]}
|
|
optionList={paymentMethodList}
|
|
allowCreate filter showClear field="payment_method" label={t('bill.title_pay_method')}
|
|
placeholder={t('base.please_select')} style={{width: '100%'}}/>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={20}>
|
|
<Col span={12}>
|
|
<Form.DatePicker
|
|
showClear
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
]}
|
|
type={'date'}
|
|
field="initiated_paid_date"
|
|
label={t('bill.title_initiated_paid_at')}
|
|
style={{width: '100%'}}
|
|
/>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.DatePicker
|
|
showClear
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
]}
|
|
type={'date'}
|
|
field="paid_date"
|
|
label={t('bill.title_paid_at')}
|
|
style={{width: '100%'}}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={20}>
|
|
<Col span={12}>
|
|
<Form.DatePicker
|
|
showClear
|
|
rules={[
|
|
{required: true, message: 'required error'},
|
|
]}
|
|
type={'date'}
|
|
field="delivered_date"
|
|
label={t('bill.title_delivered_at')}
|
|
style={{width: '100%'}}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
<div>
|
|
<BillTypeList onChange={(details) => setState({details})}/>
|
|
{state.errorMessage && <div className="semi-form-field-error-message" style={{marginBottom: 10}}>
|
|
<IconAlertCircle/>
|
|
<span style={{marginLeft: 5}}>{state.errorMessage}</span>
|
|
</div>}
|
|
<Divider/>
|
|
</div>
|
|
<div className={'text-right'} style={{margin: '20px 0'}}>
|
|
<Space spacing={12}>
|
|
<Button onClick={handleClose} type={'tertiary'}>{t('base.cancel')}</Button>
|
|
<Button
|
|
loading={state.loading} htmlType={'submit'} theme={'solid'}
|
|
type={'primary'}>{t('bill.create.confirm')}</Button>
|
|
</Space>
|
|
</div>
|
|
</Form>
|
|
</Modal>
|
|
</>)
|
|
} |