Files
BackupX/web/src/components/notifications/NotificationFormDrawer.tsx
2026-03-17 13:29:09 +08:00

185 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Alert, Button, Drawer, Input, InputNumber, Select, Space, Switch, Typography } from '@arco-design/web-react'
import { useEffect, useMemo, useState } from 'react'
import type { NotificationDetail, NotificationPayload, NotificationType } from '../../types/notifications'
import { getNotificationFieldConfigs, getNotificationTypeLabel, notificationTypeOptions } from './field-config'
interface NotificationFormDrawerProps {
visible: boolean
loading: boolean
testing: boolean
initialValue: NotificationDetail | null
onCancel: () => void
onSubmit: (value: NotificationPayload, notificationId?: number) => Promise<void>
onTest: (value: NotificationPayload, notificationId?: number) => Promise<void>
}
function createEmptyDraft(): NotificationPayload {
return {
name: '',
type: 'webhook',
enabled: true,
onSuccess: false,
onFailure: true,
config: {},
}
}
export function NotificationFormDrawer({ visible, loading, testing, initialValue, onCancel, onSubmit, onTest }: NotificationFormDrawerProps) {
const [draft, setDraft] = useState<NotificationPayload>(createEmptyDraft())
const [error, setError] = useState('')
useEffect(() => {
if (!visible) {
return
}
if (!initialValue) {
setDraft(createEmptyDraft())
setError('')
return
}
setDraft({
name: initialValue.name,
type: initialValue.type,
enabled: initialValue.enabled,
onSuccess: initialValue.onSuccess,
onFailure: initialValue.onFailure,
config: { ...initialValue.config },
})
setError('')
}, [initialValue, visible])
const fieldConfigs = useMemo(() => getNotificationFieldConfigs(draft.type), [draft.type])
function updateDraft(patch: Partial<NotificationPayload>) {
setDraft((current) => ({ ...current, ...patch }))
}
function updateConfig(key: string, value: string | number) {
setDraft((current) => ({
...current,
config: {
...current.config,
[key]: value,
},
}))
}
function validate(value: NotificationPayload) {
if (!value.name.trim()) {
return '请输入通知名称'
}
for (const field of fieldConfigs) {
if (!field.required) {
continue
}
const currentValue = value.config[field.key]
if (typeof currentValue === 'number' && currentValue > 0) {
continue
}
if (typeof currentValue === 'string' && currentValue.trim()) {
continue
}
if (initialValue?.maskedFields?.includes(field.key) && (currentValue === '' || currentValue === undefined)) {
continue
}
return `请填写${field.label}`
}
return ''
}
async function handleSubmit() {
const validationError = validate(draft)
if (validationError) {
setError(validationError)
return
}
setError('')
await onSubmit(draft, initialValue?.id)
}
async function handleTest() {
const validationError = validate(draft)
if (validationError) {
setError(validationError)
return
}
setError('')
await onTest(draft, initialValue?.id)
}
return (
<Drawer width={560} title={initialValue ? '编辑通知配置' : '新建通知配置'} visible={visible} onCancel={onCancel} unmountOnExit={false}>
<Space direction="vertical" size="large" style={{ width: '100%' }}>
{error ? <Alert type="error" content={error} /> : null}
<div>
<Typography.Text></Typography.Text>
<Input value={draft.name} placeholder="例如:生产故障通知" onChange={(value) => updateDraft({ name: value })} />
</div>
<div>
<Typography.Text></Typography.Text>
<Select value={draft.type} options={notificationTypeOptions as unknown as { label: string; value: string }[]} onChange={(value) => updateDraft({ type: value as NotificationType, config: {} })} />
</div>
<Space align="center" size="medium">
<Typography.Text></Typography.Text>
<Switch checked={draft.enabled} onChange={(checked) => updateDraft({ enabled: checked })} />
</Space>
<Space align="center" size="medium">
<Typography.Text></Typography.Text>
<Switch checked={draft.onSuccess} onChange={(checked) => updateDraft({ onSuccess: checked })} />
</Space>
<Space align="center" size="medium">
<Typography.Text></Typography.Text>
<Switch checked={draft.onFailure} onChange={(checked) => updateDraft({ onFailure: checked })} />
</Space>
<div>
<Typography.Title heading={6} style={{ marginTop: 0 }}>
{getNotificationTypeLabel(draft.type)}
</Typography.Title>
<Space direction="vertical" size="large" style={{ width: '100%' }}>
{fieldConfigs.map((field) => {
const currentValue = draft.config[field.key]
const normalizedValue = typeof currentValue === 'number' || typeof currentValue === 'string' ? currentValue : field.type === 'number' ? 0 : ''
return (
<div key={field.key}>
<Typography.Text>
{field.label}
{field.required ? ' *' : ''}
</Typography.Text>
{field.type === 'password' ? (
<Input.Password value={String(normalizedValue)} placeholder={field.placeholder} onChange={(value) => updateConfig(field.key, value)} />
) : field.type === 'number' ? (
<InputNumber style={{ width: '100%' }} value={Number(normalizedValue)} min={0} onChange={(value) => updateConfig(field.key, Number(value ?? 0))} />
) : field.type === 'textarea' ? (
<Input.TextArea value={String(normalizedValue)} placeholder={field.placeholder} onChange={(value) => updateConfig(field.key, value)} />
) : (
<Input value={String(normalizedValue)} placeholder={field.placeholder} onChange={(value) => updateConfig(field.key, value)} />
)}
{field.description ? (
<Typography.Paragraph type="secondary" style={{ marginBottom: 0, marginTop: 4 }}>
{field.description}
</Typography.Paragraph>
) : null}
{initialValue?.maskedFields?.includes(field.key) && !draft.config[field.key] ? (
<Typography.Paragraph type="secondary" style={{ marginBottom: 0, marginTop: 4 }}>
</Typography.Paragraph>
) : null}
</div>
)
})}
</Space>
</div>
<Space>
<Button loading={testing} onClick={handleTest}>
</Button>
<Button type="primary" loading={loading} onClick={handleSubmit}>
</Button>
</Space>
</Space>
</Drawer>
)
}