summaryrefslogtreecommitdiff
path: root/lib/risk-management/table/risks-update-sheet.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/risk-management/table/risks-update-sheet.tsx')
-rw-r--r--lib/risk-management/table/risks-update-sheet.tsx406
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;