summaryrefslogtreecommitdiff
path: root/lib/bidding/selection/selection-result-form.tsx
blob: 54687cc9ddcf8d6a7b174dd12fc9fd9b02624634 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
'use client'

import * as React from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { useToast } from '@/hooks/use-toast'
import { saveSelectionResult } from './actions'
import { Loader2, Save, FileText } from 'lucide-react'
import { Dropzone, DropzoneZone, DropzoneUploadIcon, DropzoneTitle, DropzoneDescription, DropzoneInput } from '@/components/ui/dropzone'

const selectionResultSchema = z.object({
  summary: z.string().min(1, '결과요약을 입력해주세요'),
})

type SelectionResultFormData = z.infer<typeof selectionResultSchema>

interface SelectionResultFormProps {
  biddingId: number
  onSuccess: () => void
}

export function SelectionResultForm({ biddingId, onSuccess }: SelectionResultFormProps) {
  const { toast } = useToast()
  const [isSubmitting, setIsSubmitting] = React.useState(false)
  const [attachmentFiles, setAttachmentFiles] = React.useState<File[]>([])

  const form = useForm<SelectionResultFormData>({
    resolver: zodResolver(selectionResultSchema),
    defaultValues: {
      summary: '',
    },
  })

  const removeAttachmentFile = (index: number) => {
    setAttachmentFiles(prev => prev.filter((_, i) => i !== index))
  }

  const onSubmit = async (data: SelectionResultFormData) => {
    setIsSubmitting(true)
    try {
      const result = await saveSelectionResult({
        biddingId,
        summary: data.summary,
        attachments: attachmentFiles
      })

      if (result.success) {
        toast({
          title: '저장 완료',
          description: result.message,
        })
        onSuccess()
      } else {
        toast({
          title: '저장 실패',
          description: result.error,
          variant: 'destructive',
        })
      }
    } catch (error) {
      console.error('Failed to save selection result:', error)
      toast({
        title: '저장 실패',
        description: '선정결과 저장 중 오류가 발생했습니다.',
        variant: 'destructive',
      })
    } finally {
      setIsSubmitting(false)
    }
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>선정결과</CardTitle>
      </CardHeader>
      <CardContent>
        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
            {/* 결과요약 */}
            <FormField
              control={form.control}
              name="summary"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>결과요약</FormLabel>
                  <FormControl>
                    <Textarea
                      placeholder="선정결과에 대한 요약을 입력해주세요..."
                      className="min-h-[120px]"
                      {...field}
                    />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />

            {/* 첨부파일 */}
            <div className="space-y-4">
              <FormLabel>첨부파일</FormLabel>
              <Dropzone
                maxSize={10 * 1024 * 1024} // 10MB
                onDropAccepted={(files) => {
                  const newFiles = Array.from(files)
                  setAttachmentFiles(prev => [...prev, ...newFiles])
                }}
                onDropRejected={() => {
                  toast({
                    title: "파일 업로드 거부",
                    description: "파일 크기 및 형식을 확인해주세요.",
                    variant: "destructive",
                  })
                }}
              >
                <DropzoneZone>
                  <DropzoneUploadIcon className="mx-auto h-12 w-12 text-muted-foreground" />
                  <DropzoneTitle className="text-lg font-medium">
                    파일을 드래그하거나 클릭하여 업로드
                  </DropzoneTitle>
                  <DropzoneDescription className="text-sm text-muted-foreground">
                    PDF, Word, Excel, 이미지 파일 (최대 10MB)
                  </DropzoneDescription>
                </DropzoneZone>
                <DropzoneInput />
              </Dropzone>

              {attachmentFiles.length > 0 && (
                <div className="space-y-2">
                  <h4 className="text-sm font-medium">업로드된 파일</h4>
                  <div className="space-y-2">
                    {attachmentFiles.map((file, index) => (
                      <div
                        key={index}
                        className="flex items-center justify-between p-3 bg-muted rounded-lg"
                      >
                        <div className="flex items-center gap-3">
                          <FileText className="h-4 w-4 text-muted-foreground" />
                          <div>
                            <p className="text-sm font-medium">{file.name}</p>
                            <p className="text-xs text-muted-foreground">
                              {(file.size / 1024 / 1024).toFixed(2)} MB
                            </p>
                          </div>
                        </div>
                        <Button
                          type="button"
                          variant="ghost"
                          size="sm"
                          onClick={() => removeAttachmentFile(index)}
                        >
                          제거
                        </Button>
                      </div>
                    ))}
                  </div>
                </div>
              )}
            </div>

            {/* 저장 버튼 */}
            <div className="flex justify-end">
              <Button type="submit" disabled={isSubmitting}>
                {isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
                <Save className="mr-2 h-4 w-4" />
                저장
              </Button>
            </div>
          </form>
        </Form>
      </CardContent>
    </Card>
  )
}