diff options
Diffstat (limited to 'lib/risk-management/table/risks-update-sheet.tsx')
| -rw-r--r-- | lib/risk-management/table/risks-update-sheet.tsx | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/lib/risk-management/table/risks-update-sheet.tsx b/lib/risk-management/table/risks-update-sheet.tsx new file mode 100644 index 00000000..727a7634 --- /dev/null +++ b/lib/risk-management/table/risks-update-sheet.tsx @@ -0,0 +1,406 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +'use client'; + +/* IMPORT */ +import { Button } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; +import { CalendarIcon } from 'lucide-react'; +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { format } from 'date-fns'; +import { getProcurementManagerList, getRiskEventsById, modifyRiskEvents } from '../service'; +import { ko } from 'date-fns/locale'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { RISK_ADMIN_COMMENTS_LIST, RISK_EVENT_TYPE_LIST, RISK_PROVIDER_LIST } from '@/config/risksConfig'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from '@/components/ui/sheet'; +import { Textarea } from '@/components/ui/textarea'; +import { toast } from 'sonner'; +import { type RisksView, type User } from '@/db/schema'; +import { useEffect, useState, useTransition } from 'react'; +import { useForm } from 'react-hook-form'; +import UserComboBox from './user-combo-box'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; + +// ---------------------------------------------------------------------------------------------------- + +/* TYPES */ +const risksUpdateFormSchema = z.object({ + eventType: z.enum(RISK_EVENT_TYPE_LIST as [string, ...string[]]), + provider: z.enum(RISK_PROVIDER_LIST as [string, ...string[]]), + occuredAt: z.date(), + content: z.string().optional(), + eventStatus: z.boolean(), + managerId: z.number().optional(), + adminComment: z.string().optional(), +}); + +type RisksUpdateFormData = z.infer<typeof risksUpdateFormSchema>; + +interface RisksUpdateSheetProps { + open: boolean, + onOpenChange: (open: boolean) => void, + riskData: RisksView, + onSuccess: () => void, +}; + +// ---------------------------------------------------------------------------------------------------- + +/* RISKS UPDATE FORM SHEET COMPONENT */ +function RisksUpdateSheet(props: RisksUpdateSheetProps) { + const { + open, + onOpenChange, + riskData, + onSuccess, + } = props; + const [isPending, startTransition] = useTransition(); + const form = useForm<RisksUpdateFormData>({ + resolver: zodResolver(risksUpdateFormSchema), + defaultValues: { + eventType: '', + provider: '', + occuredAt: new Date(), + content: '', + eventStatus: true, + managerId: undefined, + adminComment: '', + }, + }); + const watchEventStatus = form.watch('eventStatus'); + const [selectedCommentType, setSelectedCommentType] = useState(''); + const [managerList, setManagerList] = useState<Partial<User>[]>([]); + const [isLoadingManagerList, setIsLoadingManagerList] = useState(false); + + useEffect(() => { + if (open && riskData?.id) { + startTransition(async () => { + try { + const targetData = await getRiskEventsById(riskData.id); + if (targetData) { + const targetRiskEvent = targetData[0]; + form.reset({ + eventType: targetRiskEvent.eventType, + provider: targetRiskEvent.provider, + occuredAt: targetRiskEvent.occuredAt, + content: targetRiskEvent.content ?? '', + eventStatus: targetRiskEvent.eventStatus, + managerId: targetRiskEvent.managerId || undefined, + adminComment: targetRiskEvent.adminComment ?? '', + }); + setSelectedCommentType( + RISK_ADMIN_COMMENTS_LIST.includes(targetRiskEvent.adminComment ?? '') ? targetRiskEvent.adminComment! : '기타', + ); + const managerList = await getProcurementManagerList(); + setManagerList(managerList); + } + } catch (error) { + console.error('Error in Loading Risk Event for Updating:', error); + toast.error(error instanceof Error ? error.message : '편집할 데이터를 불러오는 데 실패했어요.'); + } finally { + setIsLoadingManagerList(false); + } + }); + } + }, [open, form]); + + const onSubmit = async (data: RisksUpdateFormData) => { + startTransition(async () => { + try { + const newRiskEventData = { + eventType: data.eventType, + provider: data.provider, + occuredAt: data.occuredAt, + content: data.content || null, + eventStatus: data.eventStatus, + managerId: !data.eventStatus ? null : data.managerId === 0 ? null : data.managerId, + adminComment: !data.eventStatus ? null : data.adminComment || null, + }; + await modifyRiskEvents(riskData.id, newRiskEventData); + toast.success('리스크 이벤트가 수정되었어요.'); + onSuccess(); + onOpenChange(false); + } catch (error) { + console.error('Error in Saving Risk Event:', error); + toast.error( + error instanceof Error ? error.message : '리스크 이벤트 저장 중 오류가 발생했어요.', + ); + } + }) + } + + if (!open) { + return null; + } + + return ( + <Sheet open={open} onOpenChange={onOpenChange}> + <SheetContent className="w-[900px] sm:max-w-[900px] flex flex-col" style={{width:900, height: '100vh'}}> + <SheetHeader className="flex-shrink-0 pb-4 border-b"> + <SheetTitle className="font-bold"> + 리스크 정보 관리 + </SheetTitle> + <SheetDescription> + 리스크 정보를 수정할 수 있어요. + </SheetDescription> + </SheetHeader> + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col flex-1 min-h-0"> + <div className="flex-1 overflow-y-auto py-4 min-h-0"> + <div className="space-y-6 pr-4"> + <Card> + <CardHeader> + <CardTitle>기본 정보</CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + <div className="grid grid-cols-2 gap-4"> + <FormField + control={form.control} + name="eventType" + render={({ field }) => ( + <FormItem> + <FormLabel>항목</FormLabel> + <FormControl> + <Select onValueChange={field.onChange} value={field.value || ""}> + <SelectTrigger> + <SelectValue placeholder="선택" /> + </SelectTrigger> + <SelectContent> + {RISK_EVENT_TYPE_LIST.map((option) => ( + <SelectItem key={option} value={option}> + {option} + </SelectItem> + ))} + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="provider" + render={({ field }) => ( + <FormItem> + <FormLabel>신용평가사</FormLabel> + <FormControl> + <Select onValueChange={field.onChange} value={field.value || ""}> + <SelectTrigger> + <SelectValue placeholder="선택" /> + </SelectTrigger> + <SelectContent> + {RISK_PROVIDER_LIST.map((option) => ( + <SelectItem key={option} value={option}> + {option} + </SelectItem> + ))} + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="occuredAt" + render={({ field }) => ( + <FormItem className="flex flex-col"> + <FormLabel>발생일자</FormLabel> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + variant="outline" + className={`pl-3 text-left font-normal ${!field.value && "text-muted-foreground"}`} + > + {field.value + ? format(field.value, "yyyy-MM-dd", { locale: ko }) + : "날짜 선택"} + <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value ?? undefined} + onSelect={(date) => field.onChange(date || undefined)} + locale={ko} + /> + </PopoverContent> + </Popover> + <FormMessage /> + </FormItem> + )} + /> + </div> + <FormField + control={form.control} + name="content" + render={({ field }) => ( + <FormItem> + <FormLabel>상세 내용</FormLabel> + <FormControl> + <Textarea + placeholder="상세 내용을 입력하세요." + {...field} + value={field.value ?? ''} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </CardContent> + </Card> + <Card className="w-full"> + <CardHeader> + <CardTitle>관리 정보</CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + <FormField + control={form.control} + name="eventStatus" + render={({ field }) => ( + <FormItem> + <FormLabel>리스크 해소 여부</FormLabel> + <FormControl> + <Select onValueChange={(value) => field.onChange(value === 'true')} value={String(field.value)}> + <SelectTrigger> + <SelectValue placeholder="리스크 해소 여부 선택" /> + </SelectTrigger> + <SelectContent> + <SelectItem value="true">아니오</SelectItem> + <SelectItem value="false">예</SelectItem> + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + {watchEventStatus && ( + <div className="grid grid-cols-2 gap-4"> + <FormField + control={form.control} + name="managerId" + render={({ field }) => ( + <FormItem> + <FormLabel>구매 담당자</FormLabel> + <UserComboBox + users={managerList} + value={field.value ?? null} + onChange={field.onChange} + placeholder={isLoadingManagerList ? '구매 담당자 로딩 중...' : '구매 담당자 선택...'} + disabled={isPending || isLoadingManagerList} + /> + <FormControl> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <div className="flex flex-col gap-4"> + <FormItem> + <FormLabel>관리 담당자 의견</FormLabel> + <FormControl> + <Select + onValueChange={(value) => { + setSelectedCommentType(value); + if (value !== '기타') { + form.setValue('adminComment', value); + } else { + form.setValue('adminComment', ''); + } + }} + value={selectedCommentType} + > + <SelectTrigger> + <SelectValue placeholder="의견 선택" /> + </SelectTrigger> + <SelectContent> + {RISK_ADMIN_COMMENTS_LIST.map((comment) => ( + <SelectItem key={comment} value={comment}> + {comment} + </SelectItem> + ))} + </SelectContent> + </Select> + </FormControl> + </FormItem> + {selectedCommentType === '기타' && ( + <FormField + control={form.control} + name="adminComment" + render={({ field }) => ( + <FormItem> + <FormControl> + <Textarea + placeholder="관리 담당자 의견을 입력하세요." + {...field} + value={field.value ?? ''} + /> + </FormControl> + </FormItem> + )} + /> + )} + </div> + </div> + )} + </CardContent> + </Card> + </div> + </div> + <div className="flex-shrink-0 flex justify-end gap-2 bg-background"> + <Button + type="button" + variant="outline" + onClick={() => onOpenChange(false)} + disabled={isPending} + > + 취소 + </Button> + <Button type="submit" disabled={isPending}> + {isPending ? '저장 중...' : '수정'} + </Button> + </div> + </form> + </Form> + </SheetContent> + </Sheet> + ) +} + +// ---------------------------------------------------------------------------------------------------- + +/* EXPORT */ +export default RisksUpdateSheet; |
