84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import type { StudioCategoryBrief } from '@/api/types'
|
|
import {
|
|
createCategoryBrief,
|
|
updateCategoryBrief,
|
|
type CategoryBriefPayload,
|
|
} from '@/api/modules/studio'
|
|
import { handleApiError } from '@/lib/errors'
|
|
import { CategoryPicker } from '@/features/categories/category-picker'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Label } from '@/components/ui/label'
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
|
|
interface Props {
|
|
brief: StudioCategoryBrief | null
|
|
creating: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
onSaved: () => void
|
|
}
|
|
|
|
export function CategoryBriefEditSheet({ brief, creating, onOpenChange, onSaved }: Props) {
|
|
const open = creating || brief !== null
|
|
const [categoryId, setCategoryId] = useState<string | null>(null)
|
|
const [description, setDescription] = useState('')
|
|
const [targetAudience, setTargetAudience] = useState('')
|
|
const [contentAngles, setContentAngles] = useState('')
|
|
const [riskNotes, setRiskNotes] = useState('')
|
|
const [saving, setSaving] = useState(false)
|
|
|
|
useEffect(() => {
|
|
setCategoryId(brief?.category_id ?? null)
|
|
setDescription(brief?.description ?? '')
|
|
setTargetAudience(brief?.target_audience ?? '')
|
|
setContentAngles(brief?.content_angles?.join('\n') ?? '')
|
|
setRiskNotes(brief?.risk_notes ?? '')
|
|
}, [brief, creating])
|
|
|
|
const save = async () => {
|
|
if (!categoryId) {
|
|
toast.error('请选择分类')
|
|
return
|
|
}
|
|
|
|
const payload: CategoryBriefPayload = {
|
|
category_id: categoryId,
|
|
description: description.trim() || null,
|
|
target_audience: targetAudience.trim() || null,
|
|
content_angles: contentAngles.split('\n').map((value) => value.trim()).filter(Boolean),
|
|
risk_notes: riskNotes.trim() || null,
|
|
}
|
|
|
|
setSaving(true)
|
|
try {
|
|
if (brief) await updateCategoryBrief(brief.id, payload)
|
|
else await createCategoryBrief(payload)
|
|
toast.success(brief ? '分类简报已更新' : '分类简报已创建')
|
|
onSaved()
|
|
onOpenChange(false)
|
|
} catch (error) {
|
|
handleApiError(error)
|
|
} finally {
|
|
setSaving(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
<SheetContent className="w-full overflow-y-auto sm:max-w-xl">
|
|
<SheetHeader><SheetTitle>{brief ? '编辑分类简报' : '新建分类简报'}</SheetTitle></SheetHeader>
|
|
<div className="grid gap-5 px-4 pb-8">
|
|
<div className="grid gap-2"><Label>分类</Label><CategoryPicker value={categoryId} onChange={setCategoryId} /></div>
|
|
<div className="grid gap-2"><Label htmlFor="brief-description">定位描述</Label><Textarea id="brief-description" value={description} onChange={(e) => setDescription(e.target.value)} /></div>
|
|
<div className="grid gap-2"><Label htmlFor="brief-audience">目标受众</Label><Textarea id="brief-audience" value={targetAudience} onChange={(e) => setTargetAudience(e.target.value)} /></div>
|
|
<div className="grid gap-2"><Label htmlFor="brief-angles">内容切入角度</Label><Textarea id="brief-angles" value={contentAngles} onChange={(e) => setContentAngles(e.target.value)} placeholder="每行一个角度" /></div>
|
|
<div className="grid gap-2"><Label htmlFor="brief-risks">风险提示</Label><Textarea id="brief-risks" value={riskNotes} onChange={(e) => setRiskNotes(e.target.value)} /></div>
|
|
<Button disabled={saving} onClick={save}>{saving ? '保存中…' : '保存'}</Button>
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
)
|
|
}
|