Files
kevisual-center-v1/src/pages/config/edit/List.tsx
2025-03-23 04:02:03 +08:00

298 lines
10 KiB
TypeScript

import { JSX, useEffect, useState } from 'react';
import { useConfigStore } from '../store/config';
import { CardBlank } from '@/components/card';
import { Button, ButtonGroup, Divider, Drawer, FormControlLabel, Tab, Tabs, Tooltip, useTheme } from '@mui/material';
import { Edit, Plus, Save, Share, Trash, X } from 'lucide-react';
import ShareAltOutlined from '@ant-design/icons/ShareAltOutlined';
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
import { useForm, Controller } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { TextField, TextFieldLabel } from '@kevisual/center-components/input/TextField.tsx';
import { useTranslation } from 'react-i18next';
import { IconButton } from '@kevisual/center-components/button/index.tsx';
import { useShallow } from 'zustand/shallow';
import { load, dump } from 'js-yaml';
import CodeEditor from '@uiw/react-textarea-code-editor';
import { toast } from 'react-toastify';
import { isEmpty, pick } from 'lodash-es';
import { usePermissionModal } from '@kevisual/resources/index.ts';
import React from 'react';
type DataYamlEditProps = {
onSave: (data: any) => Promise<void>;
type?: 'yaml' | 'json';
};
export const DataYamlEdit = ({ onSave, type }: DataYamlEditProps) => {
const { formData } = useConfigStore(
useShallow((state) => {
return {
formData: state.formData,
};
}),
);
const [yaml, setYaml] = useState(formData.data);
useEffect(() => {
const _fromData = formData.data || {};
if (isEmpty(_fromData)) {
setYaml('');
return;
} else {
if (type === 'yaml') {
const data = dump(_fromData);
setYaml(data);
} else {
const data = JSON.stringify(_fromData, null, 2);
setYaml(data);
}
}
}, [formData]);
const handleSave = () => {
let data: any = {};
try {
if (type === 'yaml') {
data = load(yaml);
} else {
data = JSON.parse(yaml);
}
onSave({ data });
} catch (error: any) {
console.error(error);
const errorMessage = error.message.toString();
toast.error(errorMessage || '解析失败,请检查格式');
}
};
return (
<>
<div className='flex gap-2 items-center'>
<Tooltip title='保存'>
<IconButton sx={{ width: 24, height: 24, padding: '8px' }} onClick={handleSave}>
<Save />
</IconButton>
</Tooltip>
<div className='text-sm'>{type === 'yaml' ? 'Yaml' : 'Json'} </div>
</div>
<CodeEditor value={yaml} onChange={(e) => setYaml(e.target.value)} className='w-full h-full grow' language={type === 'yaml' ? 'yaml' : 'json'} />
</>
);
};
export const DrawerEdit = () => {
const { t } = useTranslation();
const { showEdit, setShowEdit, formData, updateData } = useConfigStore(
useShallow((state) => {
return {
showEdit: state.showEdit,
setShowEdit: state.setShowEdit,
formData: state.formData,
updateData: state.updateData,
};
}),
);
const [tab, setTab] = useState<'base' | 'yaml' | 'json'>('base');
const { control, handleSubmit, reset } = useForm({
defaultValues: {
title: formData.title || '',
description: formData.description || '',
key: formData.key || '',
},
});
useEffect(() => {
if (showEdit) {
const _formData = {
id: formData.id || '',
title: formData.title || '',
description: formData.description || '',
key: formData.key || '',
};
reset(_formData);
}
}, [showEdit, formData]);
const isEdit = !!formData?.id;
const onSave = async (values: any) => {
await updateData({ ...values, id: formData.id }, { refresh: true });
};
const onSubmit = (data) => {
const pickValue = pick(data, ['title', 'key', 'description']);
onSave(pickValue);
};
const theme = useTheme();
const defaultProps = theme.components?.MuiTextField?.defaultProps;
return (
<Drawer
open={showEdit}
anchor='right'
container={document.getElementById('for-drawer')}
slotProps={{
paper: {
sx: {
width: '50%',
height: '100%',
},
},
}}>
<div className='h-full bg-white rounded-lg p-2 w-full overflow-hidden'>
<div className='text-2xl font-bold px-2 py-4 flex gap-2 items-center'>
<IconButton onClick={() => setShowEdit(false)} size='small'>
<X />
</IconButton>
<div>{formData.title}</div>
</div>
<Divider />
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
<Tab label='基本信息' value='base' />
<Tab label='Yaml Config' value='yaml' />
<Tab label='JSON Config' value='json' />
</Tabs>
{tab === 'base' && (
<form onSubmit={handleSubmit(onSubmit)} className='w-full p-2 flex flex-col gap-6'>
<Controller control={control} name='title' render={({ field }) => <TextField {...field} label='Title' />} />
<Controller
name='key'
control={control}
render={({ field }) => (
<TextField
{...field}
label={
<TextFieldLabel label='Key' tips='Key is the unique identifier for the configuration. if set and id is none will change data by key;' />
}
/>
)}
/>
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label='Description' multiline rows={4} />} />
<Button type='submit' variant='contained' color='primary'>
{t('Submit')}
</Button>
</form>
)}
{tab === 'yaml' && (
<div className='w-full flex flex-col gap-2 px-4 py-2' style={{ height: 'calc(100% - 120px)' }}>
<DataYamlEdit onSave={onSave} type='yaml' />
</div>
)}
{tab === 'json' && (
<div className='w-full flex flex-col gap-2 px-4 py-2' style={{ height: 'calc(100% - 120px)' }}>
<DataYamlEdit onSave={onSave} type='json' />
</div>
)}
</div>
</Drawer>
);
};
export const MyController = React.forwardRef(
({ control, name, Component, componentProps }: { control: any; name: string; Component: (props: any) => JSX.Element; componentProps: any }, ref) => {
const theme = useTheme();
const defaultProps = theme.components?.MuiTextField?.defaultProps;
console.log(defaultProps, 'defaultProps');
const { field } = useController({ control, name });
return <Component {...field} {...componentProps} ref={ref} />;
},
);
export const List = () => {
const { list, getConfig, setShowEdit, setFormData, deleteConfig, updateData, formData, detectConfig } = useConfigStore();
const [modal, contextHolder] = useModal();
useEffect(() => {
getConfig();
}, []);
const { setOpen, contextHolder: contextHolderPermission } = usePermissionModal({
onSave: async (values) => {
const permission = values;
console.log(permission, formData.id);
if (permission && formData.id) {
const res = await updateData({ data: { permission }, id: formData.id }, { refresh: true });
console.log(res);
return res.code === 200;
}
await new Promise((resolve) => setTimeout(resolve, 1000));
return true;
},
});
console.log(list);
const { t } = useTranslation();
return (
<div className='w-full h-full flex bg-gray-100'>
<div className='h-full bg-white'>
<div className='p-2'>
<IconButton
onClick={() => {
setShowEdit(true);
setFormData({});
}}>
<Plus size={16} />
</IconButton>
</div>
</div>
<div className=' grow p-4 '>
<div className='flex pb-4 gap-4'>
<Tooltip title={t('从config的json的配置的数据同步过来。')}>
<Button
variant='contained'
color='primary'
size='small'
onClick={() => {
detectConfig();
}}>
{t('Detect')}
</Button>
</Tooltip>
</div>
<div className='w-full bg-white rounded-lg p-2 scrollbar ' style={{ height: 'calc(100% - 80px)' }}>
<div className='flex flex-wrap gap-2 '>
{list.map((item) => (
<div className='card w-[300px] ' key={item.id}>
<div className='card-title flex font-bold justify-between'>{item.title}</div>
<div className='card-content'>
<div className='flex gap-2'>
<div>{t('Description')}:</div>
<div>{item.description}</div>
</div>
<div className='flex gap-2'>
<div>{t('Key')}:</div>
<div>{item.key}</div>
</div>
</div>
<div className='card-footer flex justify-end mt-1'>
<ButtonGroup variant='contained'>
<Button
onClick={() => {
setShowEdit(true);
setFormData(item);
}}>
<Edit />
</Button>
<Tooltip title={t('Permission')}>
<Button
onClick={() => {
setFormData(item);
setOpen(true, item.data?.permission);
}}>
<ShareAltOutlined style={{ fontSize: 20 }} />
</Button>
</Tooltip>
<Button
onClick={() => {
modal.confirm({
title: 'Delete',
content: 'Are you sure delete this data?',
onOk: () => {
deleteConfig(item.id);
},
});
}}>
<Trash />
</Button>
</ButtonGroup>
</div>
</div>
))}
<CardBlank />
</div>
</div>
</div>
<DrawerEdit />
{contextHolder}
{contextHolderPermission}
</div>
);
};