From ee77f36b1ceece1236d45fba102c3ea410acebc1 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 11 Sep 2025 11:20:42 +0000
Subject: (최겸) 구매 계약 메인 및 상세 기능 개발(템플릿 연동 및 계약 전달 개발
 필요)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../detail/general-contract-offset-details.tsx     | 314 +++++++++++++++++++++
 1 file changed, 314 insertions(+)
 create mode 100644 lib/general-contracts/detail/general-contract-offset-details.tsx

(limited to 'lib/general-contracts/detail/general-contract-offset-details.tsx')

diff --git a/lib/general-contracts/detail/general-contract-offset-details.tsx b/lib/general-contracts/detail/general-contract-offset-details.tsx
new file mode 100644
index 00000000..af4f2ef2
--- /dev/null
+++ b/lib/general-contracts/detail/general-contract-offset-details.tsx
@@ -0,0 +1,314 @@
+'use client'
+
+import React, { useState, useEffect } from 'react'
+import { useSession } from 'next-auth/react'
+import { Input } from '@/components/ui/input'
+import { Button } from '@/components/ui/button'
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'
+import { Checkbox } from '@/components/ui/checkbox'
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
+import { Plus, Trash2, Save, LoaderIcon, RotateCcw } from 'lucide-react'
+import { getOffsetDetails, updateOffsetDetails } from '../service'
+import { toast } from 'sonner'
+
+interface OffsetDetailsProps {
+  contractId: number
+  contractType?: string
+}
+
+interface OffsetDetailItem {
+  id: string
+  project: string
+  poNumber: string
+  poItemDescription: string
+  offsetReason: string
+  contractCurrency: string
+  contractAmount: string
+  offsetDate: string
+  remark: string
+}
+
+export function OffsetDetails({ contractId }: OffsetDetailsProps) {
+  const session = useSession()
+  const [isLoading, setIsLoading] = useState(false)
+  const [isEnabled, setIsEnabled] = useState(true)
+  
+  // 특정 계약종류를 제외한 일반계약은 Default로 표시
+  const isDisabled = false
+  
+  const [offsetDetails, setOffsetDetails] = useState<OffsetDetailItem[]>([])
+
+  // 회입/상계사유 옵션
+  const offsetReasonOptions = [
+    '판매자 사양불만족',
+    '납기지연',
+    '품질불량',
+    '계약조건변경',
+    '기타'
+  ]
+
+  // 초기 데이터 로드
+  useEffect(() => {
+    const loadOffsetDetails = async () => {
+      try {
+        const data = await getOffsetDetails(contractId)
+        if (data && data.enabled !== undefined) {
+          setIsEnabled(data.enabled)
+          setOffsetDetails(data.offsetDetails || [])
+        } else {
+        }
+      } catch (error) {
+        console.error('회입/상계내역 데이터 로드 실패:', error)
+        toast.error('회입/상계내역 데이터를 불러오는데 실패했습니다.')
+      }
+    }
+
+    loadOffsetDetails()
+  }, [contractId])
+
+  const addOffsetDetailRow = () => {
+    const newRow: OffsetDetailItem = {
+      id: Date.now().toString(),
+      project: '',
+      poNumber: '',
+      poItemDescription: '',
+      offsetReason: '',
+      contractCurrency: 'KRW',
+      contractAmount: '',
+      offsetDate: '',
+      remark: ''
+    }
+    setOffsetDetails([...offsetDetails, newRow])
+  }
+
+  const removeOffsetDetailRow = (id: string) => {
+    setOffsetDetails(offsetDetails.filter(item => item.id !== id))
+  }
+
+  const updateOffsetDetailData = (id: string, field: keyof OffsetDetailItem, value: string) => {
+    setOffsetDetails(prev => 
+      prev.map(item => 
+        item.id === id ? { ...item, [field]: value } : item
+      )
+    )
+  }
+
+  const handleSaveOffsetDetails = async () => {
+    if (!session.data?.user?.id) {
+      toast.error('로그인이 필요합니다.')
+      return
+    }
+
+    setIsLoading(true)
+    try {
+      const offsetDetailsData = {
+        enabled: isEnabled,
+        offsetDetails: offsetDetails
+      }
+
+      await updateOffsetDetails(contractId, offsetDetailsData, Number(session.data.user.id))
+      toast.success('회입/상계내역이 성공적으로 저장되었습니다.')
+    } catch (error) {
+      console.error('회입/상계내역 저장 실패:', error)
+      toast.error('회입/상계내역 저장에 실패했습니다.')
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  return (
+    <div className="w-full">
+      <Accordion type="single" collapsible className="w-full">
+        <AccordionItem value="offset-details">
+          <AccordionTrigger className="hover:no-underline">
+            <div className="flex items-center gap-3 w-full">
+              <RotateCcw className="w-5 h-5" />
+              <span className="font-medium">회입/상계내역</span>
+            </div>
+          </AccordionTrigger>
+          <AccordionContent>
+            <div className="space-y-6">
+              {/* 체크박스 */}
+              <div className="flex items-center gap-2">
+                <Checkbox 
+                  checked={isEnabled}
+                  disabled={isDisabled}
+                  onCheckedChange={(checked) => {
+                    if (!isDisabled) {
+                      setIsEnabled(checked as boolean)
+                    }
+                  }}
+                />
+                <span className="text-sm font-medium">회입/상계내역 활성화</span>
+              </div>
+
+              {/* 회입/상계내역 테이블 */}
+              <div className="space-y-4">
+                <div className="flex items-center justify-between">
+                  <h3 className="text-lg font-medium">회입/상계내역</h3>
+                  <div className="flex gap-2">
+                    <Button
+                      type="button"
+                      variant="outline"
+                      size="sm"
+                      onClick={addOffsetDetailRow}
+                      disabled={isDisabled || !isEnabled}
+                      className="flex items-center gap-2"
+                    >
+                      <Plus className="w-4 h-4" />
+                      행 추가
+                    </Button>
+                    <Button
+                      type="button"
+                      variant="outline"
+                      size="sm"
+                      onClick={() => {
+                        if (confirm('선택된 행들을 삭제하시겠습니까?')) {
+                          // 선택된 행들 삭제 로직 (필요시 구현)
+                        }
+                      }}
+                      disabled={isDisabled || !isEnabled}
+                      className="flex items-center gap-2"
+                    >
+                      <Trash2 className="w-4 h-4" />
+                      행 삭제
+                    </Button>
+                  </div>
+                </div>
+                
+                <div className="overflow-x-auto">
+                  <table className={`w-full border-collapse border border-gray-300 ${!isEnabled ? 'opacity-50' : ''}`}>
+                    <thead>
+                      <tr className="bg-yellow-100">
+                        <th className="border border-gray-300 p-2 w-16">No.</th>
+                        <th className="border border-gray-300 p-2 w-32">프로젝트</th>
+                        <th className="border border-gray-300 p-2 w-40">발주번호</th>
+                        <th className="border border-gray-300 p-2">발주품목(Description)</th>
+                        <th className="border border-gray-300 p-2 w-40">회입/상계사유</th>
+                        <th className="border border-gray-300 p-2 w-32">계약통화</th>
+                        <th className="border border-gray-300 p-2 w-32">계약금액</th>
+                        <th className="border border-gray-300 p-2 w-32">회입/상계일</th>
+                        <th className="border border-gray-300 p-2">비고</th>
+                        <th className="border border-gray-300 p-2 w-20">Action</th>
+                      </tr>
+                    </thead>
+                    <tbody>
+                      {offsetDetails.map((item, index) => (
+                        <tr key={item.id} className="bg-yellow-50">
+                          <td className="border border-gray-300 p-2 text-center">{index + 1}</td>
+                          <td className="border border-gray-300 p-2">
+                            <Input
+                              value={item.project}
+                              onChange={(e) => updateOffsetDetailData(item.id, 'project', e.target.value)}
+                              disabled={isDisabled || !isEnabled}
+                              className="border-0 bg-transparent p-0 h-auto"
+                            />
+                          </td>
+                          <td className="border border-gray-300 p-2">
+                            <Input
+                              value={item.poNumber}
+                              onChange={(e) => updateOffsetDetailData(item.id, 'poNumber', e.target.value)}
+                              disabled={isDisabled || !isEnabled}
+                              className="border-0 bg-transparent p-0 h-auto"
+                            />
+                          </td>
+                          <td className="border border-gray-300 p-2">
+                            <Input
+                              value={item.poItemDescription}
+                              onChange={(e) => updateOffsetDetailData(item.id, 'poItemDescription', e.target.value)}
+                              disabled={isDisabled || !isEnabled}
+                              className="border-0 bg-transparent p-0 h-auto"
+                            />
+                          </td>
+                          <td className="border border-gray-300 p-2">
+                            <Select
+                              value={item.offsetReason}
+                              onValueChange={(value) => updateOffsetDetailData(item.id, 'offsetReason', value)}
+                              disabled={isDisabled || !isEnabled}
+                            >
+                              <SelectTrigger className="border-0 bg-transparent p-0 h-auto">
+                                <SelectValue placeholder="선택하세요" />
+                              </SelectTrigger>
+                              <SelectContent>
+                                {offsetReasonOptions.map((option) => (
+                                  <SelectItem key={option} value={option}>
+                                    {option}
+                                  </SelectItem>
+                                ))}
+                              </SelectContent>
+                            </Select>
+                          </td>
+                          <td className="border border-gray-300 p-2">
+                            <Input
+                              value={item.contractCurrency}
+                              onChange={(e) => updateOffsetDetailData(item.id, 'contractCurrency', e.target.value)}
+                              disabled={isDisabled || !isEnabled}
+                              className="border-0 bg-transparent p-0 h-auto"
+                            />
+                          </td>
+                          <td className="border border-gray-300 p-2">
+                            <Input
+                              value={item.contractAmount}
+                              onChange={(e) => updateOffsetDetailData(item.id, 'contractAmount', e.target.value)}
+                              disabled={isDisabled || !isEnabled}
+                              className="border-0 bg-transparent p-0 h-auto"
+                            />
+                          </td>
+                          <td className="border border-gray-300 p-2">
+                            <Input
+                              type="date"
+                              value={item.offsetDate}
+                              onChange={(e) => updateOffsetDetailData(item.id, 'offsetDate', e.target.value)}
+                              disabled={isDisabled || !isEnabled}
+                              className="border-0 bg-transparent p-0 h-auto"
+                            />
+                          </td>
+                          <td className="border border-gray-300 p-2">
+                            <Input
+                              value={item.remark}
+                              onChange={(e) => updateOffsetDetailData(item.id, 'remark', e.target.value)}
+                              disabled={isDisabled || !isEnabled}
+                              className="border-0 bg-transparent p-0 h-auto"
+                            />
+                          </td>
+                          <td className="border border-gray-300 p-2 text-center">
+                            <Button
+                              type="button"
+                              variant="ghost"
+                              size="sm"
+                              onClick={() => removeOffsetDetailRow(item.id)}
+                              disabled={isDisabled || !isEnabled}
+                              className="h-8 w-8 p-0 text-red-600 hover:text-red-700"
+                            >
+                              <Trash2 className="w-4 h-4" />
+                            </Button>
+                          </td>
+                        </tr>
+                      ))}
+                    </tbody>
+                  </table>
+                </div>
+              </div>
+
+              {/* 저장 버튼 */}
+              <div className="flex justify-end pt-4 border-t">
+                <Button 
+                  onClick={handleSaveOffsetDetails} 
+                  disabled={isLoading || isDisabled || !isEnabled}
+                  className="flex items-center gap-2"
+                >
+                  {isLoading ? (
+                    <LoaderIcon className="w-4 h-4 animate-spin" />
+                  ) : (
+                    <Save className="w-4 h-4" />
+                  )}
+                  회입/상계내역 저장
+                </Button>
+              </div>
+            </div>
+          </AccordionContent>
+        </AccordionItem>
+      </Accordion>
+    </div>
+  )
+}
-- 
cgit v1.2.3

