| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import { |
| | | CreateBase, |
| | | useTranslate, |
| | | TextInput, |
| | | NumberInput, |
| | | BooleanInput, |
| | | DateInput, |
| | | SaveButton, |
| | | SelectInput, |
| | | Toolbar, |
| | | required, |
| | | useDataProvider, |
| | | useNotify, |
| | | Form, |
| | | useCreateController, |
| | | } from 'react-admin'; |
| | | import { |
| | | Dialog, |
| | |
| | | Stack, |
| | | Grid, |
| | | Box, |
| | | Stepper, |
| | | Step, |
| | | StepLabel, |
| | | StepContent, |
| | | Button, |
| | | TextField, |
| | | InputAdornment, |
| | | IconButton, |
| | | } from '@mui/material'; |
| | | import { useForm, Controller, useWatch, FormProvider, useFormContext } from "react-hook-form"; |
| | | import { matchPath, useLocation } from 'react-router'; |
| | | import DialogCloseButton from "../components/DialogCloseButton"; |
| | | import StatusSelectInput from "../components/StatusSelectInput"; |
| | | import MemoInput from "../components/MemoInput"; |
| | | import Visibility from '@mui/icons-material/Visibility'; |
| | | import VisibilityOff from '@mui/icons-material/VisibilityOff'; |
| | | import request from '@/utils/request'; |
| | | |
| | | const TenantCreate = (props) => { |
| | | const { open, setOpen } = props; |
| | | |
| | | const translate = useTranslate(); |
| | | const notify = useNotify(); |
| | | const { |
| | | control, |
| | | handleSubmit, |
| | | watch, |
| | | setValue, |
| | | getValues, |
| | | reset, |
| | | formState: { |
| | | errors, |
| | | isDirty, |
| | | }, |
| | | trigger |
| | | } = useForm(); |
| | | |
| | | const passwordVal = watch('password'); |
| | | |
| | | const [activeStep, setActiveStep] = useState(0); |
| | | const [showPassword, setShowPassword] = useState(false); |
| | | |
| | | const validateCurrentStep = async () => { |
| | | let fieldsToValidate = []; |
| | | if (activeStep === 0) { |
| | | fieldsToValidate = ['name', 'flag']; |
| | | } else if (activeStep === 1) { |
| | | fieldsToValidate = ['username', 'email', 'password', 'confirmPassword']; |
| | | } else if (activeStep === 2) { |
| | | fieldsToValidate = ['memo']; |
| | | } |
| | | return await trigger(fieldsToValidate); |
| | | }; |
| | | |
| | | const handleNext = async () => { |
| | | const isValid = await validateCurrentStep(); |
| | | if (!isValid) { |
| | | return; |
| | | } |
| | | setActiveStep(prev => prev + 1); |
| | | }; |
| | | |
| | | const handleBack = () => { |
| | | setActiveStep(prev => prev - 1); |
| | | }; |
| | | |
| | | const handleClose = (event, reason) => { |
| | | if (reason !== "backdropClick") { |
| | | setActiveStep(0); |
| | | setOpen(false); |
| | | reset(); |
| | | } |
| | | }; |
| | | |
| | | const handleSuccess = async (data) => { |
| | | setOpen(false); |
| | | notify('common.response.success'); |
| | | }; |
| | | |
| | | const handleError = async (error) => { |
| | | notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } }); |
| | | }; |
| | | const onSubmit = (data) => { |
| | | request.post('/tenant/init', data).then(res => { |
| | | const { code, msg, data } = res.data; |
| | | if (code === 200) { |
| | | notify(msg, { type: 'success', messageArgs: { _: msg } }); |
| | | setOpen(false); |
| | | reset(); |
| | | } else { |
| | | notify(msg, { type: 'error', messageArgs: { _: msg } }); |
| | | } |
| | | }).catch((error) => { |
| | | notify(error.message, { type: 'error', messageArgs: { _: error.message } }); |
| | | console.error(error); |
| | | }) |
| | | } |
| | | |
| | | return ( |
| | | <> |
| | | <CreateBase |
| | | record={{}} |
| | | transform={(data) => { |
| | | return data; |
| | | }} |
| | | mutationOptions={{ onSuccess: handleSuccess, onError: handleError }} |
| | | <Dialog |
| | | open={open} |
| | | onClose={handleClose} |
| | | aria-labelledby="form-dialog-title" |
| | | fullWidth |
| | | disableRestoreFocus |
| | | maxWidth="md" // 'xs' | 'sm' | 'md' | 'lg' | 'xl' |
| | | > |
| | | <Dialog |
| | | open={open} |
| | | onClose={handleClose} |
| | | aria-labelledby="form-dialog-title" |
| | | fullWidth |
| | | disableRestoreFocus |
| | | maxWidth="md" // 'xs' | 'sm' | 'md' | 'lg' | 'xl' |
| | | > |
| | | <Form> |
| | | <DialogTitle id="form-dialog-title" sx={{ |
| | | position: 'sticky', |
| | | top: 0, |
| | | backgroundColor: 'background.paper', |
| | | zIndex: 1000 |
| | | }} |
| | | > |
| | | {translate('create.title')} |
| | | <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}> |
| | | <DialogCloseButton onClose={handleClose} /> |
| | | </Box> |
| | | </DialogTitle> |
| | | <DialogContent> |
| | | <Grid container rowSpacing={2} columnSpacing={2}> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <TextInput |
| | | label="table.field.tenant.name" |
| | | source="name" |
| | | parse={v => v} |
| | | autoFocus |
| | | validate={required()} |
| | | /> |
| | | </Grid> |
| | | <DialogTitle id="form-dialog-title" sx={{ |
| | | position: 'sticky', |
| | | top: 0, |
| | | backgroundColor: 'background.paper', |
| | | zIndex: 1000 |
| | | }}> |
| | | {translate('create.title')} |
| | | <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}> |
| | | <DialogCloseButton onClose={handleClose} /> |
| | | </Box> |
| | | </DialogTitle> |
| | | <DialogContent sx={{ mt: 2 }}> |
| | | {open && ( |
| | | <form noValidate onSubmit={handleSubmit(onSubmit)} > |
| | | <Stepper activeStep={activeStep} orientation="vertical"> |
| | | <Step> |
| | | <StepLabel>{translate('page.tenant.create.title.basic')}</StepLabel> |
| | | <StepContent> |
| | | <Stack spacing={3} mt={2} direction='column' width={'50%'}> |
| | | <Controller |
| | | name="name" |
| | | control={control} |
| | | defaultValue="" |
| | | rules={{ required: true }} |
| | | parse={v => v} |
| | | render={({ field, fieldState: { error } }) => ( |
| | | <TextField |
| | | {...field} |
| | | label={translate('table.field.tenant.name')} |
| | | variant="outlined" |
| | | autoComplete="off" |
| | | error={!!error} |
| | | helperText={error ? translate('ra.validation.required') : ""} |
| | | /> |
| | | )} |
| | | /> |
| | | <Controller |
| | | name="flag" |
| | | control={control} |
| | | defaultValue="" |
| | | rules={{ |
| | | required: { |
| | | value: true, |
| | | message: translate('ra.validation.required') |
| | | }, |
| | | pattern: { |
| | | value: /^[A-Za-z]{3,20}$/, |
| | | message: translate('page.tenant.create.tip.onlyEn'), |
| | | } |
| | | }} |
| | | parse={v => v} |
| | | render={({ field, fieldState: { error } }) => ( |
| | | <TextField |
| | | {...field} |
| | | label={translate('table.field.tenant.flag')} |
| | | variant="outlined" |
| | | autoComplete="off" |
| | | error={!!error} |
| | | helperText={error ? error.message : ""} |
| | | /> |
| | | )} |
| | | /> |
| | | </Stack> |
| | | <Box sx={{ mt: 3 }}> |
| | | <Button onClick={handleNext} variant="contained"> |
| | | {translate('page.tenant.create.btn.next')} |
| | | </Button> |
| | | <Button disabled={activeStep === 0} onClick={handleBack}> |
| | | {translate('page.tenant.create.btn.back')} |
| | | </Button> |
| | | </Box> |
| | | </StepContent> |
| | | </Step> |
| | | |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <StatusSelectInput /> |
| | | </Grid> |
| | | <Grid item xs={12} display="flex" gap={1}> |
| | | <Stack direction="column" spacing={1} width={'100%'}> |
| | | <MemoInput /> |
| | | </Stack> |
| | | </Grid> |
| | | </Grid> |
| | | </DialogContent> |
| | | <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}> |
| | | <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }} > |
| | | <SaveButton /> |
| | | </Toolbar> |
| | | </DialogActions> |
| | | </Form> |
| | | </Dialog> |
| | | </CreateBase> |
| | | <Step> |
| | | <StepLabel>{translate('page.tenant.create.title.root')}</StepLabel> |
| | | <StepContent> |
| | | <Stack spacing={3} mt={2} direction='column' width={'50%'}> |
| | | <Controller |
| | | name="username" |
| | | control={control} |
| | | defaultValue="" |
| | | rules={{ |
| | | required: { |
| | | value: true, |
| | | message: translate('ra.validation.required') |
| | | }, |
| | | pattern: { |
| | | value: /^[A-Za-z0-9]{3,20}$/, |
| | | message: translate('page.settings.resetPwd.tip.usernameLimit'), |
| | | }, |
| | | }} |
| | | parse={v => v} |
| | | render={({ field, fieldState: { error } }) => ( |
| | | <TextField |
| | | {...field} |
| | | label={translate('table.field.user.username')} |
| | | variant="outlined" |
| | | autoComplete="off" |
| | | error={!!error} |
| | | helperText={error ? error.message : ""} |
| | | /> |
| | | )} |
| | | /> |
| | | <Controller |
| | | name="email" |
| | | control={control} |
| | | defaultValue="" |
| | | rules={{ |
| | | required: false, |
| | | pattern: { |
| | | value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, |
| | | message: translate("ra.validation.email"), |
| | | }, |
| | | }} |
| | | render={({ field, fieldState: { error } }) => ( |
| | | <TextField |
| | | {...field} |
| | | label={translate('table.field.user.email')} |
| | | variant="outlined" |
| | | autoComplete="off" |
| | | error={!!error} |
| | | helperText={error ? error.message : ""} |
| | | /> |
| | | )} |
| | | /> |
| | | <Controller |
| | | name="password" |
| | | control={control} |
| | | defaultValue="" |
| | | rules={{ |
| | | required: { |
| | | value: true, |
| | | message: translate('ra.validation.required'), |
| | | }, |
| | | pattern: { |
| | | value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,13}$/, |
| | | message: translate('page.settings.resetPwd.tip.pwdInputLimit'), |
| | | }, |
| | | }} |
| | | render={({ field, fieldState: { error } }) => ( |
| | | <TextField |
| | | {...field} |
| | | label={translate('page.settings.resetPwd.newPwd')} |
| | | type={showPassword ? 'text' : 'password'} |
| | | variant="outlined" |
| | | autoComplete="off" |
| | | error={!!error} |
| | | helperText={error ? error.message : ""} |
| | | InputProps={{ |
| | | endAdornment: ( |
| | | <InputAdornment position="end"> |
| | | <IconButton |
| | | aria-label="toggle password visibility" |
| | | onClick={() => setShowPassword((show) => !show)} |
| | | onMouseDown={(event) => { event.preventDefault() }} |
| | | edge="end" |
| | | > |
| | | {showPassword ? <VisibilityOff /> : <Visibility />} |
| | | </IconButton> |
| | | </InputAdornment> |
| | | ), |
| | | }} |
| | | /> |
| | | )} |
| | | /> |
| | | <Controller |
| | | name="confirmPassword" |
| | | control={control} |
| | | defaultValue="" |
| | | rules={{ |
| | | required: translate('ra.validation.required'), |
| | | validate: value => |
| | | value === passwordVal || translate('page.settings.resetPwd.tip.pwdNotMatch'), |
| | | }} |
| | | render={({ field, fieldState: { error } }) => ( |
| | | <TextField |
| | | {...field} |
| | | label={translate('page.settings.resetPwd.confirmNewPwd')} |
| | | type={showPassword ? 'text' : 'password'} |
| | | variant="outlined" |
| | | autoComplete="off" |
| | | error={!!error} |
| | | helperText={error?.message || ""} |
| | | InputProps={{ |
| | | endAdornment: ( |
| | | <InputAdornment position="end"> |
| | | <IconButton |
| | | aria-label="toggle password visibility" |
| | | onClick={() => setShowPassword((show) => !show)} |
| | | onMouseDown={(event) => { event.preventDefault() }} |
| | | edge="end" |
| | | > |
| | | {showPassword ? <VisibilityOff /> : <Visibility />} |
| | | </IconButton> |
| | | </InputAdornment> |
| | | ), |
| | | }} |
| | | /> |
| | | )} |
| | | /> |
| | | </Stack> |
| | | <Box sx={{ mt: 3 }}> |
| | | <Button onClick={handleNext} variant="contained"> |
| | | {translate('page.tenant.create.btn.next')} |
| | | </Button> |
| | | <Button onClick={handleBack}> |
| | | {translate('page.tenant.create.btn.back')} |
| | | </Button> |
| | | </Box> |
| | | </StepContent> |
| | | </Step> |
| | | |
| | | <Step> |
| | | <StepLabel>{translate('page.tenant.create.title.confirm')}</StepLabel> |
| | | <StepContent> |
| | | <Stack spacing={3} mt={2} direction='column' width={'50%'}> |
| | | <Controller |
| | | name="memo" |
| | | control={control} |
| | | defaultValue="" |
| | | rules={{ required: false }} |
| | | parse={v => v} |
| | | render={({ field, fieldState: { error } }) => ( |
| | | <TextField |
| | | {...field} |
| | | label={translate('common.field.memo')} |
| | | variant="outlined" |
| | | autoComplete="off" |
| | | fullWidth |
| | | multiline |
| | | minRows={2} |
| | | error={!!error} |
| | | helperText={error ? translate('ra.validation.required') : ""} |
| | | /> |
| | | )} |
| | | /> |
| | | </Stack> |
| | | <Box sx={{ mt: 3 }}> |
| | | <Button type="submit" variant="contained"> |
| | | {translate('ra.action.save')} |
| | | </Button> |
| | | <Button onClick={handleBack}> |
| | | {translate('page.tenant.create.btn.back')} |
| | | </Button> |
| | | </Box> |
| | | </StepContent> |
| | | </Step> |
| | | </Stepper> |
| | | </form> |
| | | )} |
| | | </DialogContent> |
| | | <DialogActions sx={{ height: 10, position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}> |
| | | </DialogActions> |
| | | </Dialog> |
| | | </> |
| | | ) |
| | | } |