4.8.10 test (#2470)

* i18n

* perf: invoice type

* fix: helper line change error

* perf: base64 image

* perf: app list ui

* perf: upload max size check

* perf: init system plugin

* perf: dataset list ui

* perf: http node ui

* perf: ui

* perf: invoice tip

* fix: ts

* perf: invoice table

* perf: null check
This commit is contained in:
Archer
2024-08-22 13:43:19 +08:00
committed by GitHub
parent 19904e648b
commit b3acd570f7
33 changed files with 576 additions and 552 deletions

View File

@@ -409,7 +409,7 @@ const PlanUsage = () => {
return standardPlan ? (
<Box mt={[6, 0]}>
<Flex fontSize={'lg'} h={'30px'}>
<Flex fontSize={['md', 'lg']} h={'30px'}>
<Flex alignItems={'center'}>
<MyIcon mr={2} name={'support/account/plans'} w={'20px'} />
{t('common:support.wallet.subscription.Team plan and usage')}
@@ -428,7 +428,7 @@ const PlanUsage = () => {
borderColor={'borderColor.low'}
borderRadius={'md'}
>
<Flex px={[5, 7]} py={[3, 6]}>
<Flex px={[5, 7]} py={[3, 6]} whiteSpace={'nowrap'}>
<Box flex={'1 0 0'}>
<Box color={'myGray.600'} fontSize="sm">
{t('common:support.wallet.subscription.Current plan')}
@@ -475,8 +475,10 @@ const PlanUsage = () => {
>
<Flex>
<Flex flex={'1 0 0'} alignItems={'flex-end'}>
<Box fontSize={'md'}>{t('common:info.resource')}</Box>
<Box fontSize={'xs'} color={'myGray.500'}>
<Box fontSize={'md'} fontWeight={'bold'} color={'myGray.900'}>
{t('common:info.resource')}
</Box>
<Box ml={1} display={['none', 'block']} fontSize={'xs'} color={'myGray.500'}>
{t('common:info.include')}
</Box>
</Flex>

View File

@@ -32,52 +32,25 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
import { getTeamInvoiceHeader } from '@/web/support/user/team/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useRouter } from 'next/router';
import { useForm } from 'react-hook-form';
type chosenBillDataType = {
_id: string;
price: number;
};
const ApplyInvoiceModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const [chosenBillDataList, setChosenBillDataList] = useState<chosenBillDataType[]>([]);
const [totalPrice, setTotalPrice] = useState(0);
const [formData, setFormData] = useState<TeamInvoiceHeaderType>({
teamName: '',
unifiedCreditCode: '',
companyAddress: '',
companyPhone: '',
bankName: '',
bankAccount: '',
needSpecialInvoice: undefined,
emailAddress: ''
});
const router = useRouter();
const {
isOpen: isOpenSettleModal,
onOpen: onOpenSettleModal,
onClose: onCloseSettleModal
} = useDisclosure();
const handleChange = useCallback((e: any) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
}, []);
const handleRatiosChange = useCallback((v: string) => {
setFormData((prev) => ({ ...prev, needSpecialInvoice: v === 'true' }));
}, []);
const isHeaderValid = useCallback((v: TeamInvoiceHeaderType) => {
const emailRegex = /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/;
for (const [key, value] of Object.entries(v)) {
if (typeof value === 'string' && value.trim() === '') {
return false;
}
}
return emailRegex.test(v.emailAddress);
}, []);
const {
loading: isLoading,
data: billsList,
@@ -86,12 +59,23 @@ const ApplyInvoiceModal = ({ onClose }: { onClose: () => void }) => {
manual: false
});
const { run: handleSubmitInvoice, loading: isSubmitting } = useRequest2(
() =>
const handleSingleCheck = useCallback(
(item: invoiceBillDataType) => {
if (chosenBillDataList.find((bill) => bill._id === item._id)) {
setChosenBillDataList(chosenBillDataList.filter((bill) => bill._id !== item._id));
} else {
setChosenBillDataList([...chosenBillDataList, { _id: item._id, price: item.price }]);
}
},
[chosenBillDataList]
);
const { runAsync: onSubmitApply, loading: isSubmitting } = useRequest2(
(data) =>
submitInvoice({
amount: totalPrice,
billIdList: chosenBillDataList.map((item) => item._id),
...formData
...data
}),
{
manual: true,
@@ -104,39 +88,29 @@ const ApplyInvoiceModal = ({ onClose }: { onClose: () => void }) => {
}
);
const inputForm = useForm<TeamInvoiceHeaderType>({
defaultValues: {
teamName: '',
unifiedCreditCode: '',
companyAddress: '',
companyPhone: '',
bankName: '',
bankAccount: '',
needSpecialInvoice: false,
emailAddress: ''
}
});
const { loading: isLoadingHeader } = useRequest2(() => getTeamInvoiceHeader(), {
manual: false,
onSuccess: (res) => setFormData(res)
onSuccess: (res) => inputForm.reset(res)
});
const handleSubmit = useCallback(async () => {
if (!isHeaderValid(formData)) {
toast({
title: t('common:support.wallet.invoice_data.in_valid'),
status: 'info'
});
return;
}
handleSubmitInvoice();
}, [formData, handleSubmitInvoice, isHeaderValid, t, toast]);
const handleBack = useCallback(() => {
setChosenBillDataList([]);
getInvoiceBills();
onCloseSettleModal();
}, [getInvoiceBills, onCloseSettleModal]);
const handleSingleCheck = useCallback(
(item: invoiceBillDataType) => {
if (chosenBillDataList.find((bill) => bill._id === item._id)) {
setChosenBillDataList(chosenBillDataList.filter((bill) => bill._id !== item._id));
} else {
setChosenBillDataList([...chosenBillDataList, { _id: item._id, price: item.price }]);
}
},
[chosenBillDataList]
);
return (
<MyModal
isOpen={true}
@@ -258,11 +232,7 @@ const ApplyInvoiceModal = ({ onClose }: { onClose: () => void }) => {
</Box>
<MyBox isLoading={isLoadingHeader}>
<Flex justify={'center'}>
<InvoiceHeaderSingleForm
formData={formData}
handleChange={handleChange}
handleRatiosChange={handleRatiosChange}
/>
<InvoiceHeaderSingleForm inputForm={inputForm} />
</Flex>
</MyBox>
<Flex
@@ -289,7 +259,7 @@ const ApplyInvoiceModal = ({ onClose }: { onClose: () => void }) => {
</Box>
</Flex>
</Button>
<Button isLoading={isSubmitting} px="0" onClick={handleSubmit}>
<Button isLoading={isSubmitting} px="0" onClick={inputForm.handleSubmit(onSubmitApply)}>
<Flex alignItems={'center'}>
<Box px={'1.25rem'} py={'0.5rem'}>
{t('common:common.Confirm')}

View File

@@ -6,19 +6,22 @@ import { useTranslation } from 'next-i18next';
import ApplyInvoiceModal from './ApplyInvoiceModal';
import { useRouter } from 'next/router';
export const InvoiceTabEnum = {
bill: 'bill',
invoice: 'invoice',
invoiceHeader: 'invoiceHeader'
};
export enum InvoiceTabEnum {
bill = 'bill',
invoice = 'invoice',
invoiceHeader = 'invoiceHeader'
}
const BillTable = dynamic(() => import('./BillTable'));
const InvoiceHeaderForm = dynamic(() => import('./InvoiceHeaderForm'));
const InvoiceTable = dynamic(() => import('./InvoiceTable'));
const BillAndInvoice = () => {
const [isOpenInvoiceModal, setIsOpenInvoiceModal] = useState(false);
const router = useRouter();
const invoiceTab = (router.query.invoiceTab as string) || InvoiceTabEnum.bill;
const { t } = useTranslation();
const router = useRouter();
const { invoiceTab = InvoiceTabEnum.bill } = router.query as { invoiceTab: `${InvoiceTabEnum}` };
const [isOpenInvoiceModal, setIsOpenInvoiceModal] = useState(false);
return (
<>
<Box p={['1rem', '2rem']}>
@@ -36,7 +39,7 @@ const BillAndInvoice = () => {
onChange={(e) => {
router.replace({
query: {
currentTab: router.query.currentTab,
...router.query,
invoiceTab: e
}
});

View File

@@ -101,6 +101,7 @@ const BillTable = () => {
isLoading={isLoading || isRefreshing}
position={'relative'}
h={'100%'}
minH={'50vh'}
overflow={'overlay'}
>
<TableContainer>
@@ -198,6 +199,10 @@ function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: ()
<FormLabel flex={'0 0 120px'}>{t('common:support.wallet.usage.Time')}:</FormLabel>
<Box>{dayjs(bill.createTime).format('YYYY/MM/DD HH:mm:ss')}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<FormLabel flex={'0 0 120px'}>{t('common:support.wallet.bill.Type')}:</FormLabel>
<Box>{t(billTypeMap[bill.type]?.label as any)}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<FormLabel flex={'0 0 120px'}>{t('common:support.wallet.bill.Status')}:</FormLabel>
<Box>{t(billStatusMap[bill.status]?.label as any)}</Box>
@@ -212,14 +217,18 @@ function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: ()
<FormLabel flex={'0 0 120px'}>{t('common:support.wallet.Amount')}:</FormLabel>
<Box>{commonT('common:pay.yuan', { amount: formatStorePrice2Read(bill.price) })}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<FormLabel flex={'0 0 120px'}>{t('common:support.wallet.bill.Type')}:</FormLabel>
<Box>{t(billTypeMap[bill.type]?.label as any)}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<FormLabel flex={'0 0 120px'}>{t('common:support.wallet.has_invoice')}:</FormLabel>
<Box>{bill.hasInvoice ? t('common:yes') : t('common:no')}</Box>
</Flex>
{bill.metadata && (
<Flex alignItems={'center'} pb={4}>
<FormLabel flex={'0 0 120px'}>{t('common:support.wallet.has_invoice')}:</FormLabel>
{bill.metadata.payWay === 'balance' ? (
t('user:bill.not_need_invoice')
) : (
<Box>
{(bill.metadata.payWay = bill.hasInvoice ? t('common:yes') : t('common:no'))}
</Box>
)}
</Flex>
)}
{!!bill.metadata?.subMode && (
<Flex alignItems={'center'} pb={4}>
<FormLabel flex={'0 0 120px'}>

View File

@@ -1,56 +1,28 @@
import Divider from '@/pages/app/detail/components/WorkflowComponents/Flow/components/Divider';
import { getTeamInvoiceHeader, updateTeamInvoiceHeader } from '@/web/support/user/team/api';
import { Box, Button, Flex, Input, Radio, RadioGroup, Stack } from '@chakra-ui/react';
import { Box, Button, Flex, HStack, Input, InputProps, Radio, RadioGroup } from '@chakra-ui/react';
import { TeamInvoiceHeaderType } from '@fastgpt/global/support/user/team/type';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useCallback, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
const InputItem = ({
label,
value,
onChange,
name
}: {
label: string;
value: string;
onChange: (e: any) => void;
name: string;
}) => {
return (
<>
<Flex justify={'space-between'} flexDir={['column', 'row']}>
<Box fontSize={'14px'} lineHeight={'2rem'}>
{label}
</Box>
<Input
bg={'myGray.50'}
border={'1px solid'}
borderColor={'myGray.200'}
w={'21.25rem'}
focusBorderColor="myGray.200"
placeholder={label}
value={value}
onChange={onChange}
name={name}
/>
</Flex>
</>
);
};
import { UseFormReturn, useForm } from 'react-hook-form';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
export const InvoiceHeaderSingleForm = ({
formData,
handleChange,
handleRatiosChange
inputForm
}: {
formData: TeamInvoiceHeaderType;
handleChange: (e: any) => void;
handleRatiosChange: (v: string) => void;
inputForm: UseFormReturn<TeamInvoiceHeaderType, any>;
}) => {
const { t } = useTranslation();
const { watch, register } = inputForm;
const needSpecialInvoice = watch('needSpecialInvoice');
const styles: InputProps = {
bg: 'myGray.50',
w: '21.25rem'
};
return (
<>
<Flex
@@ -61,138 +33,185 @@ export const InvoiceHeaderSingleForm = ({
color={'myGray.900'}
fontSize={'14px'}
>
<InputItem
label={t('common:support.wallet.invoice_data.organization_name')}
value={formData.teamName}
onChange={handleChange}
name="teamName"
/>
<InputItem
label={t('common:support.wallet.invoice_data.unit_code')}
value={formData.unifiedCreditCode}
onChange={handleChange}
name="unifiedCreditCode"
/>
<InputItem
label={t('common:support.wallet.invoice_data.company_address')}
value={formData.companyAddress}
onChange={handleChange}
name="companyAddress"
/>
<InputItem
label={t('common:support.wallet.invoice_data.company_phone')}
value={formData.companyPhone}
onChange={handleChange}
name="companyPhone"
/>
<InputItem
label={t('common:support.wallet.invoice_data.bank')}
value={formData.bankName}
onChange={handleChange}
name="bankName"
/>
<InputItem
label={t('common:support.wallet.invoice_data.bank_account')}
value={formData.bankAccount}
onChange={handleChange}
name="bankAccount"
/>
<Flex justify={'space-between'} flexDir={['column', 'row']}>
<Box fontSize={'14px'} lineHeight={'2rem'}>
<Flex
justify={'space-between'}
alignItems={['flex-start', 'center']}
flexDir={['column', 'row']}
>
<FormLabel required>
{t('common:support.wallet.invoice_data.organization_name')}
</FormLabel>
<Input
{...styles}
placeholder={t('common:support.wallet.invoice_data.organization_name')}
{...register('teamName', { required: true })}
/>
</Flex>
<Flex
justify={'space-between'}
alignItems={['flex-start', 'center']}
flexDir={['column', 'row']}
>
<FormLabel required>{t('common:support.wallet.invoice_data.unit_code')}</FormLabel>
<Input
{...styles}
placeholder={t('common:support.wallet.invoice_data.unit_code')}
{...register('unifiedCreditCode', { required: true })}
/>
</Flex>
<Flex
justify={'space-between'}
alignItems={['flex-start', 'center']}
flexDir={['column', 'row']}
>
<FormLabel required={!!needSpecialInvoice}>
{t('common:support.wallet.invoice_data.company_address')}
</FormLabel>
<Input
{...styles}
placeholder={t('common:support.wallet.invoice_data.company_address')}
{...register('companyAddress', { required: !!needSpecialInvoice })}
/>
</Flex>
<Flex
justify={'space-between'}
alignItems={['flex-start', 'center']}
flexDir={['column', 'row']}
>
<FormLabel required={!!needSpecialInvoice}>
{t('common:support.wallet.invoice_data.company_phone')}
</FormLabel>
<Input
{...styles}
placeholder={t('common:support.wallet.invoice_data.company_phone')}
{...register('companyPhone', { required: !!needSpecialInvoice })}
/>
</Flex>
<Flex
justify={'space-between'}
alignItems={['flex-start', 'center']}
flexDir={['column', 'row']}
>
<FormLabel required={!!needSpecialInvoice}>
{t('common:support.wallet.invoice_data.bank')}
</FormLabel>
<Input
{...styles}
placeholder={t('common:support.wallet.invoice_data.bank')}
{...register('bankName', { required: !!needSpecialInvoice })}
/>
</Flex>
<Flex
justify={'space-between'}
alignItems={['flex-start', 'center']}
flexDir={['column', 'row']}
>
<FormLabel required={!!needSpecialInvoice}>
{t('common:support.wallet.invoice_data.bank_account')}
</FormLabel>
<Input
{...styles}
placeholder={t('common:support.wallet.invoice_data.bank_account')}
{...register('bankAccount', { required: !!needSpecialInvoice })}
/>
</Flex>
<Flex
justify={'space-between'}
alignItems={['flex-start', 'center']}
flexDir={['column', 'row']}
>
<FormLabel required>
{t('common:support.wallet.invoice_data.need_special_invoice')}
</Box>
</FormLabel>
{/* @ts-ignore */}
<RadioGroup
value={
formData.needSpecialInvoice === undefined
? ''
: formData.needSpecialInvoice.toString()
}
onChange={handleRatiosChange}
value={`${needSpecialInvoice}`}
onChange={(e) => {
inputForm.setValue('needSpecialInvoice', e === 'true');
}}
w={'21.25rem'}
>
<Stack direction="row" h={'2rem'}>
<HStack h={'2rem'}>
<Radio value="true" pr={'1rem'}>
<Box fontSize={'14px'}>{t('common:yes')}</Box>
</Radio>
<Radio value="false">
<Box fontSize={'14px'}>{t('common:no')}</Box>
</Radio>
</Stack>
</HStack>
</RadioGroup>
</Flex>
<Box w={'100%'}>
<Divider showBorderBottom={false} />
</Box>
<InputItem
label={t('common:support.wallet.invoice_data.email')}
value={formData.emailAddress}
onChange={handleChange}
name="emailAddress"
/>
<Flex
justify={'space-between'}
alignItems={['flex-start', 'center']}
flexDir={['column', 'row']}
>
<FormLabel required>{t('common:support.wallet.invoice_data.email')}</FormLabel>
<Input
{...styles}
placeholder={t('common:support.wallet.invoice_data.email')}
{...register('emailAddress', {
required: true,
pattern: {
value: /(^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$)/,
message: t('user:password.email_phone_error')
}
})}
/>
</Flex>
</Flex>
</>
);
};
const InvoiceHeaderForm = () => {
const [formData, setFormData] = useState<TeamInvoiceHeaderType>({
teamName: '',
unifiedCreditCode: '',
companyAddress: '',
companyPhone: '',
bankName: '',
bankAccount: '',
needSpecialInvoice: undefined,
emailAddress: ''
const inputForm = useForm<TeamInvoiceHeaderType>({
defaultValues: {
teamName: '',
unifiedCreditCode: '',
companyAddress: '',
companyPhone: '',
bankName: '',
bankAccount: '',
needSpecialInvoice: false,
emailAddress: ''
}
});
const { loading: isLoading } = useRequest2(() => getTeamInvoiceHeader(), {
manual: false,
onSuccess: (data) => {
setFormData(data);
console.log(data, '--');
inputForm.reset(data);
}
});
const { t } = useTranslation();
const { toast } = useToast();
const handleChange = useCallback((e: any) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
}, []);
const handleRatiosChange = useCallback((v: string) => {
setFormData((prev) => ({ ...prev, needSpecialInvoice: v === 'true' }));
}, []);
const isHeaderValid = useCallback((v: TeamInvoiceHeaderType) => {
const emailRegex = /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/;
return emailRegex.test(v.emailAddress);
}, []);
const { loading: isSubmitting, run: handleSubmit } = useRequest2(
() => updateTeamInvoiceHeader(formData),
const { loading: isSubmitting, runAsync: onUpdateHeader } = useRequest2(
(data: TeamInvoiceHeaderType) => updateTeamInvoiceHeader(data),
{
manual: true,
successToast: t('common:common.Save Success'),
errorToast: t('common:common.Save Failed')
}
);
const onSubmit = useCallback(() => {
if (!isHeaderValid(formData)) {
toast({
title: t('common:support.wallet.invoice_data.in_valid'),
status: 'info'
});
return;
}
handleSubmit();
}, [handleSubmit, formData, isHeaderValid, toast, t]);
return (
<>
<MyBox isLoading={isLoading} pt={['1rem', '3.5rem']}>
<Flex w={'100%'} overflow={'auto'} justify={'center'} flexDir={'column'} align={'center'}>
<InvoiceHeaderSingleForm
formData={formData}
handleChange={handleChange}
handleRatiosChange={handleRatiosChange}
/>
<InvoiceHeaderSingleForm inputForm={inputForm} />
<Flex w={'100%'} justify={'center'} mt={'3rem'}>
<Button variant={'primary'} px="0" onClick={onSubmit} isLoading={isSubmitting}>
<Button
variant={'primary'}
px="0"
onClick={inputForm.handleSubmit(onUpdateHeader)}
isLoading={isSubmitting}
>
<Flex alignItems={'center'} px={'20px'}>
<Box px={'1.25rem'} py={'0.5rem'}>
{t('common:common.Save')}

View File

@@ -197,11 +197,11 @@ function InvoiceDetailModal({
);
}
function LabelItem({ label, value }: { label: string; value: string }) {
function LabelItem({ label, value }: { label: string; value?: string }) {
return (
<Flex alignItems={'center'} justify={'space-between'}>
<FormLabel flex={'0 0 120px'}>{label}</FormLabel>
<Box>{value}</Box>
<Box>{value || '-'}</Box>
</Flex>
);
}