summaryrefslogtreecommitdiff
path: root/lib/general-contracts_old/main
diff options
context:
space:
mode:
Diffstat (limited to 'lib/general-contracts_old/main')
-rw-r--r--lib/general-contracts_old/main/create-general-contract-dialog.tsx413
-rw-r--r--lib/general-contracts_old/main/general-contract-update-sheet.tsx401
-rw-r--r--lib/general-contracts_old/main/general-contracts-table-columns.tsx571
-rw-r--r--lib/general-contracts_old/main/general-contracts-table-toolbar-actions.tsx124
-rw-r--r--lib/general-contracts_old/main/general-contracts-table.tsx217
5 files changed, 1726 insertions, 0 deletions
diff --git a/lib/general-contracts_old/main/create-general-contract-dialog.tsx b/lib/general-contracts_old/main/create-general-contract-dialog.tsx
new file mode 100644
index 00000000..2c3fc8bc
--- /dev/null
+++ b/lib/general-contracts_old/main/create-general-contract-dialog.tsx
@@ -0,0 +1,413 @@
+"use client"
+
+import * as React from "react"
+import { useRouter } from "next/navigation"
+import { Plus } from "lucide-react"
+import { toast } from "sonner"
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Textarea } from "@/components/ui/textarea"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Calendar } from "@/components/ui/calendar"
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
+import { CalendarIcon } from "lucide-react"
+import { format } from "date-fns"
+import { ko } from "date-fns/locale"
+import { cn } from "@/lib/utils"
+import { createContract, getVendors, getProjects } from "@/lib/general-contracts/service"
+import {
+ GENERAL_CONTRACT_CATEGORIES,
+ GENERAL_CONTRACT_TYPES,
+ GENERAL_EXECUTION_METHODS
+} from "@/lib/general-contracts/types"
+import { useSession } from "next-auth/react"
+
+interface CreateContractForm {
+ contractNumber: string
+ name: string
+ category: string
+ type: string
+ executionMethod: string
+ vendorId: number | null
+ projectId: number | null
+ startDate: Date | undefined
+ endDate: Date | undefined
+ validityEndDate: Date | undefined
+ notes: string
+}
+
+export function CreateGeneralContractDialog() {
+ const router = useRouter()
+ const { data: session } = useSession()
+ const [open, setOpen] = React.useState(false)
+ const [isLoading, setIsLoading] = React.useState(false)
+ const [vendors, setVendors] = React.useState<Array<{ id: number; vendorName: string; vendorCode: string | null }>>([])
+ const [projects, setProjects] = React.useState<Array<{ id: number; code: string; name: string; type: string }>>([])
+
+ const [form, setForm] = React.useState<CreateContractForm>({
+ contractNumber: '',
+ name: '',
+ category: '',
+ type: '',
+ executionMethod: '',
+ vendorId: null,
+ projectId: null,
+ startDate: undefined,
+ endDate: undefined,
+ validityEndDate: undefined,
+ notes: '',
+ })
+
+ // 업체 목록 조회
+ React.useEffect(() => {
+ const fetchVendors = async () => {
+ try {
+ const vendorList = await getVendors()
+ setVendors(vendorList)
+ } catch (error) {
+ console.error('Error fetching vendors:', error)
+ }
+ }
+ fetchVendors()
+ }, [])
+
+ // 프로젝트 목록 조회
+ React.useEffect(() => {
+ const fetchProjects = async () => {
+ try {
+ const projectList = await getProjects()
+ console.log(projectList)
+ setProjects(projectList)
+ } catch (error) {
+ console.error('Error fetching projects:', error)
+ }
+ }
+ fetchProjects()
+ }, [])
+
+ const handleSubmit = async () => {
+ // 필수 필드 검증
+ if (!form.name || !form.category || !form.type || !form.executionMethod ||
+ !form.vendorId || !form.startDate || !form.endDate) {
+ toast.error("필수 항목을 모두 입력해주세요.")
+ return
+ }
+
+ if (!form.validityEndDate) {
+ setForm(prev => ({ ...prev, validityEndDate: form.endDate }))
+ }
+
+ try {
+ setIsLoading(true)
+
+ const contractData = {
+ contractNumber: '',
+ name: form.name,
+ category: form.category,
+ type: form.type,
+ executionMethod: form.executionMethod,
+ projectId: form.projectId,
+ contractSourceType: 'manual',
+ vendorId: form.vendorId!,
+ startDate: form.startDate!.toISOString().split('T')[0],
+ endDate: form.endDate!.toISOString().split('T')[0],
+ validityEndDate: (form.validityEndDate || form.endDate!).toISOString().split('T')[0],
+ status: 'Draft',
+ registeredById: session?.user?.id || 1,
+ lastUpdatedById: session?.user?.id || 1,
+ notes: form.notes,
+ }
+
+ await createContract(contractData)
+
+ toast.success("새 계약이 생성되었습니다.")
+ setOpen(false)
+ resetForm()
+
+ // 상세 페이지로 이동
+ router.refresh()
+ } catch (error) {
+ console.error('Error creating contract:', error)
+ toast.error("계약 생성 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const resetForm = () => {
+ setForm({
+ contractNumber: '',
+ name: '',
+ category: '',
+ type: '',
+ executionMethod: '',
+ vendorId: null,
+ projectId: null,
+ startDate: undefined,
+ endDate: undefined,
+ validityEndDate: undefined,
+ notes: '',
+ })
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={(newOpen) => {
+ setOpen(newOpen)
+ if (!newOpen) resetForm()
+ }}>
+ <DialogTrigger asChild>
+ <Button size="sm">
+ <Plus className="mr-2 h-4 w-4" />
+ 신규등록
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
+ <DialogHeader>
+ <DialogTitle>새 계약 등록</DialogTitle>
+ <DialogDescription>
+ 새로운 계약의 기본 정보를 입력하세요.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="grid gap-4 py-4">
+ <div className="grid grid-cols-1 gap-4">
+ <div className="grid gap-2">
+ <Label htmlFor="name">계약명 *</Label>
+ <Input
+ id="name"
+ value={form.name}
+ onChange={(e) => setForm(prev => ({ ...prev, name: e.target.value }))}
+ placeholder="계약명을 입력하세요"
+ />
+ </div>
+ </div>
+
+ <div className="grid grid-cols-3 gap-4">
+ <div className="grid gap-2">
+ <Label htmlFor="category">계약구분 *</Label>
+ <Select value={form.category} onValueChange={(value) => setForm(prev => ({ ...prev, category: value }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="계약구분 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {GENERAL_CONTRACT_CATEGORIES.map((category) => {
+ const categoryLabels = {
+ 'unit_price': '단가계약',
+ 'general': '일반계약',
+ 'sale': '매각계약'
+ }
+ return (
+ <SelectItem key={category} value={category}>
+ {category} - {categoryLabels[category as keyof typeof categoryLabels]}
+ </SelectItem>
+ )
+ })}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="type">계약종류 *</Label>
+ <Select value={form.type} onValueChange={(value) => setForm(prev => ({ ...prev, type: value }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="계약종류 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {GENERAL_CONTRACT_TYPES.map((type) => {
+ const typeLabels = {
+ 'UP': '자재단가계약',
+ 'LE': '임대차계약',
+ 'IL': '개별운송계약',
+ 'AL': '연간운송계약',
+ 'OS': '외주용역계약',
+ 'OW': '도급계약',
+ 'IS': '검사계약',
+ 'LO': 'LOI',
+ 'FA': 'FA',
+ 'SC': '납품합의계약',
+ 'OF': '클레임상계계약',
+ 'AW': '사전작업합의',
+ 'AD': '사전납품합의',
+ 'AM': '설계계약',
+ 'SC_SELL': '폐기물매각계약'
+ }
+ return (
+ <SelectItem key={type} value={type}>
+ {type} - {typeLabels[type as keyof typeof typeLabels]}
+ </SelectItem>
+ )
+ })}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="executionMethod">체결방식 *</Label>
+ <Select value={form.executionMethod} onValueChange={(value) => setForm(prev => ({ ...prev, executionMethod: value }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="체결방식 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {GENERAL_EXECUTION_METHODS.map((method) => (
+ <SelectItem key={method} value={method}>
+ {method}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="project">프로젝트</Label>
+ <Select value={form.projectId?.toString()} onValueChange={(value) => setForm(prev => ({ ...prev, projectId: parseInt(value) }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="프로젝트 선택 (선택사항)" />
+ </SelectTrigger>
+ <SelectContent>
+ {projects.map((project) => (
+ <SelectItem key={project.id} value={project.id.toString()}>
+ {project.name} ({project.code})
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="vendor">협력업체 *</Label>
+ <Select value={form.vendorId?.toString()} onValueChange={(value) => setForm(prev => ({ ...prev, vendorId: parseInt(value) }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="협력업체 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {vendors.map((vendor) => (
+ <SelectItem key={vendor.id} value={vendor.id.toString()}>
+ {vendor.vendorName} ({vendor.vendorCode})
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="grid grid-cols-3 gap-4">
+ <div className="grid gap-2">
+ <Label>계약시작일 *</Label>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ className={cn(
+ "justify-start text-left font-normal",
+ !form.startDate && "text-muted-foreground"
+ )}
+ >
+ <CalendarIcon className="mr-2 h-4 w-4" />
+ {form.startDate ? format(form.startDate, "yyyy-MM-dd", { locale: ko }) : "날짜 선택"}
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-auto p-0">
+ <Calendar
+ mode="single"
+ selected={form.startDate}
+ onSelect={(date) => setForm(prev => ({ ...prev, startDate: date }))}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ </div>
+
+ <div className="grid gap-2">
+ <Label>계약종료일 *</Label>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ className={cn(
+ "justify-start text-left font-normal",
+ !form.endDate && "text-muted-foreground"
+ )}
+ >
+ <CalendarIcon className="mr-2 h-4 w-4" />
+ {form.endDate ? format(form.endDate, "yyyy-MM-dd", { locale: ko }) : "날짜 선택"}
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-auto p-0">
+ <Calendar
+ mode="single"
+ selected={form.endDate}
+ onSelect={(date) => setForm(prev => ({ ...prev, endDate: date }))}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ </div>
+
+ <div className="grid gap-2">
+ <Label>유효기간종료일</Label>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ className={cn(
+ "justify-start text-left font-normal",
+ !form.validityEndDate && "text-muted-foreground"
+ )}
+ >
+ <CalendarIcon className="mr-2 h-4 w-4" />
+ {form.validityEndDate ? format(form.validityEndDate, "yyyy-MM-dd", { locale: ko }) : "날짜 선택"}
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-auto p-0">
+ <Calendar
+ mode="single"
+ selected={form.validityEndDate}
+ onSelect={(date) => setForm(prev => ({ ...prev, validityEndDate: date }))}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ </div>
+ </div>
+ <div className="grid gap-2">
+ <Label htmlFor="notes">비고</Label>
+ <Textarea
+ id="notes"
+ value={form.notes}
+ onChange={(e) => setForm(prev => ({ ...prev, notes: e.target.value }))}
+ placeholder="비고사항을 입력하세요"
+ rows={3}
+ />
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => setOpen(false)}
+ >
+ 취소
+ </Button>
+ <Button
+ type="button"
+ onClick={handleSubmit}
+ disabled={isLoading}
+ >
+ {isLoading ? '생성 중...' : '생성'}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}
diff --git a/lib/general-contracts_old/main/general-contract-update-sheet.tsx b/lib/general-contracts_old/main/general-contract-update-sheet.tsx
new file mode 100644
index 00000000..54f4ae4e
--- /dev/null
+++ b/lib/general-contracts_old/main/general-contract-update-sheet.tsx
@@ -0,0 +1,401 @@
+"use client"
+
+import * as React from "react"
+import { useForm } from "react-hook-form"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { z } from "zod"
+import { toast } from "sonner"
+import { Button } from "@/components/ui/button"
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+} from "@/components/ui/sheet"
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+import {
+ GENERAL_CONTRACT_CATEGORIES,
+ GENERAL_CONTRACT_TYPES,
+ GENERAL_EXECUTION_METHODS,
+} from "@/lib/general-contracts/types"
+import { updateContract } from "../service"
+import { GeneralContractListItem } from "./general-contracts-table-columns"
+import { useSession } from "next-auth/react"
+const updateContractSchema = z.object({
+ category: z.string().min(1, "계약구분을 선택해주세요"),
+ type: z.string().min(1, "계약종류를 선택해주세요"),
+ executionMethod: z.string().min(1, "체결방식을 선택해주세요"),
+ name: z.string().min(1, "계약명을 입력해주세요"),
+ startDate: z.string().min(1, "계약시작일을 선택해주세요"),
+ endDate: z.string().min(1, "계약종료일을 선택해주세요"),
+ validityEndDate: z.string().min(1, "유효기간종료일을 선택해주세요"),
+ contractScope: z.string().min(1, "계약확정범위를 선택해주세요"),
+ notes: z.string().optional(),
+ linkedRfqOrItb: z.string().optional(),
+ linkedPoNumber: z.string().optional(),
+ linkedBidNumber: z.string().optional(),
+})
+
+type UpdateContractFormData = z.infer<typeof updateContractSchema>
+
+interface GeneralContractUpdateSheetProps {
+ contract: GeneralContractListItem | null
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ onSuccess?: () => void
+}
+
+export function GeneralContractUpdateSheet({
+ contract,
+ open,
+ onOpenChange,
+ onSuccess,
+}: GeneralContractUpdateSheetProps) {
+ const [isSubmitting, setIsSubmitting] = React.useState(false)
+ const session = useSession()
+ const userId = session.data?.user?.id ? Number(session.data.user.id) : null
+ const form = useForm<UpdateContractFormData>({
+ resolver: zodResolver(updateContractSchema),
+ defaultValues: {
+ category: "",
+ type: "",
+ executionMethod: "",
+ name: "",
+ startDate: "",
+ endDate: "",
+ validityEndDate: "",
+ contractScope: "",
+ notes: "",
+ linkedRfqOrItb: "",
+ linkedPoNumber: "",
+ linkedBidNumber: "",
+ },
+ })
+
+ // 계약확정범위에 따른 품목정보 필드 비활성화 여부
+ const watchedContractScope = form.watch("contractScope")
+ const isItemsDisabled = watchedContractScope === '단가' || watchedContractScope === '물량(실적)'
+
+ // 계약 데이터가 변경될 때 폼 초기화
+ React.useEffect(() => {
+ if (contract) {
+ console.log("Loading contract data:", contract)
+ const formData = {
+ category: contract.category || "",
+ type: contract.type || "",
+ executionMethod: contract.executionMethod || "",
+ name: contract.name || "",
+ startDate: contract.startDate || "",
+ endDate: contract.endDate || "",
+ validityEndDate: contract.validityEndDate || "",
+ contractScope: contract.contractScope || "",
+ notes: contract.notes || "",
+ linkedRfqOrItb: contract.linkedRfqOrItb || "",
+ linkedPoNumber: contract.linkedPoNumber || "",
+ linkedBidNumber: contract.linkedBidNumber || "",
+ }
+ console.log("Form data to reset:", formData)
+ form.reset(formData)
+ }
+ }, [contract, form])
+
+ const onSubmit = async (data: UpdateContractFormData) => {
+ if (!contract) return
+
+ try {
+ setIsSubmitting(true)
+
+ await updateContract(contract.id, {
+ category: data.category,
+ type: data.type,
+ executionMethod: data.executionMethod,
+ name: data.name,
+ startDate: data.startDate,
+ endDate: data.endDate,
+ validityEndDate: data.validityEndDate,
+ contractScope: data.contractScope,
+ notes: data.notes,
+ linkedRfqOrItb: data.linkedRfqOrItb,
+ linkedPoNumber: data.linkedPoNumber,
+ linkedBidNumber: data.linkedBidNumber,
+ vendorId: contract.vendorId,
+ lastUpdatedById: userId,
+ })
+
+ toast.success("계약 정보가 성공적으로 수정되었습니다.")
+ onOpenChange(false)
+ onSuccess?.()
+ } catch (error) {
+ console.error("Error updating contract:", error)
+ toast.error("계약 정보 수정 중 오류가 발생했습니다.")
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ return (
+ <Sheet open={open} onOpenChange={onOpenChange}>
+ <SheetContent className="w-[800px] sm:max-w-[800px] flex flex-col" style={{width: 800, maxWidth: 800, height: '100vh'}}>
+ <SheetHeader className="flex-shrink-0">
+ <SheetTitle>계약 정보 수정</SheetTitle>
+ <SheetDescription>
+ 계약의 기본 정보를 수정합니다. 변경사항은 즉시 저장됩니다.
+ </SheetDescription>
+ </SheetHeader>
+
+ <div className="flex-1 overflow-y-auto min-h-0">
+ <Form {...form}>
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 h-full">
+ <div className="grid gap-4 py-4">
+ {/* 계약구분 */}
+ <FormField
+ control={form.control}
+ name="category"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>계약구분 *</FormLabel>
+ <Select onValueChange={field.onChange} value={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="계약구분을 선택하세요" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ {GENERAL_CONTRACT_CATEGORIES.map((category) => {
+ const categoryLabels = {
+ 'unit_price': '단가계약',
+ 'general': '일반계약',
+ 'sale': '매각계약'
+ }
+ return (
+ <SelectItem key={category} value={category}>
+ {category} - {categoryLabels[category as keyof typeof categoryLabels]}
+ </SelectItem>
+ )})}
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 계약종류 */}
+ <FormField
+ control={form.control}
+ name="type"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>계약종류 *</FormLabel>
+ <Select onValueChange={field.onChange} value={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="계약종류를 선택하세요" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ {GENERAL_CONTRACT_TYPES.map((type) => {
+ const typeLabels = {
+ 'UP': '자재단가계약',
+ 'LE': '임대차계약',
+ 'IL': '개별운송계약',
+ 'AL': '연간운송계약',
+ 'OS': '외주용역계약',
+ 'OW': '도급계약',
+ 'IS': '검사계약',
+ 'LO': 'LOI',
+ 'FA': 'FA',
+ 'SC': '납품합의계약',
+ 'OF': '클레임상계계약',
+ 'AW': '사전작업합의',
+ 'AD': '사전납품합의',
+ 'AM': '설계계약',
+ 'SC_SELL': '폐기물매각계약'
+ }
+ return (
+ <SelectItem key={type} value={type}>
+ {type} - {typeLabels[type as keyof typeof typeLabels]}
+ </SelectItem>
+ )})}
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 체결방식 */}
+ <FormField
+ control={form.control}
+ name="executionMethod"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>체결방식 *</FormLabel>
+ <Select onValueChange={field.onChange} value={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="체결방식을 선택하세요" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ {GENERAL_EXECUTION_METHODS.map((method) => {
+ const methodLabels = {
+ '전자계약': '전자계약',
+ '오프라인계약': '오프라인계약'
+ }
+ return (
+ <SelectItem key={method} value={method}>
+ {method} - {methodLabels[method as keyof typeof methodLabels]}
+ </SelectItem>
+ )})}
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 계약명 */}
+ <FormField
+ control={form.control}
+ name="name"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>계약명 *</FormLabel>
+ <FormControl>
+ <Input placeholder="계약명을 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 계약시작일 */}
+ <FormField
+ control={form.control}
+ name="startDate"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>계약시작일 *</FormLabel>
+ <FormControl>
+ <Input type="date" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 계약종료일 */}
+ <FormField
+ control={form.control}
+ name="endDate"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>계약종료일 *</FormLabel>
+ <FormControl>
+ <Input type="date" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 유효기간종료일 */}
+ <FormField
+ control={form.control}
+ name="validityEndDate"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>유효기간종료일 *</FormLabel>
+ <FormControl>
+ <Input type="date" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ {/* 계약확정범위 */}
+ <FormField
+ control={form.control}
+ name="contractScope"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>계약확정범위 *</FormLabel>
+ <Select onValueChange={field.onChange} value={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="계약확정범위를 선택하세요" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="단가">단가</SelectItem>
+ <SelectItem value="금액">금액</SelectItem>
+ <SelectItem value="물량(실적)">물량(실적)</SelectItem>
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ <p className="text-sm text-muted-foreground">
+ 해당 계약으로 확정되는 범위를 선택하세요.
+ </p>
+ </FormItem>
+ )}
+ />
+
+ {/* 비고 */}
+ <FormField
+ control={form.control}
+ name="notes"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>비고</FormLabel>
+ <FormControl>
+ <Textarea
+ placeholder="비고를 입력하세요"
+ className="min-h-[100px]"
+ {...field}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ <SheetFooter className="flex-shrink-0 mt-6">
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => onOpenChange(false)}
+ disabled={isSubmitting}
+ >
+ 취소
+ </Button>
+ <Button type="submit" disabled={isSubmitting}>
+ {isSubmitting ? "수정 중..." : "수정"}
+ </Button>
+ </SheetFooter>
+ </form>
+ </Form>
+ </div>
+ </SheetContent>
+ </Sheet>
+ )
+}
diff --git a/lib/general-contracts_old/main/general-contracts-table-columns.tsx b/lib/general-contracts_old/main/general-contracts-table-columns.tsx
new file mode 100644
index 00000000..a08d8b81
--- /dev/null
+++ b/lib/general-contracts_old/main/general-contracts-table-columns.tsx
@@ -0,0 +1,571 @@
+"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import {
+ Eye, Edit, MoreHorizontal
+} from "lucide-react"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { DataTableRowAction } from "@/types/table"
+import { formatDate } from "@/lib/utils"
+
+// 일반계약 리스트 아이템 타입 정의
+export interface GeneralContractListItem {
+ id: number
+ contractNumber: string
+ revision: number
+ status: string
+ category: string
+ type: string
+ executionMethod: string
+ name: string
+ contractSourceType?: string
+ startDate: string
+ endDate: string
+ validityEndDate?: string
+ contractScope?: string
+ specificationType?: string
+ specificationManualText?: string
+ contractAmount?: number | string | null
+ totalAmount?: number | string | null
+ currency?: string
+ registeredAt: string
+ signedAt?: string
+ linkedPoNumber?: string
+ linkedRfqOrItb?: string
+ linkedBidNumber?: string
+ lastUpdatedAt: string
+ notes?: string
+ vendorId?: number
+ vendorName?: string
+ vendorCode?: string
+ projectId?: number
+ projectName?: string
+ projectCode?: string
+ managerName?: string
+ lastUpdatedByName?: string
+}
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<GeneralContractListItem> | null>>
+}
+
+// 상태별 배지 색상
+const getStatusBadgeVariant = (status: string) => {
+ switch (status) {
+ case 'Draft':
+ return 'outline'
+ case 'Request to Review':
+ case 'Confirm to Review':
+ return 'secondary'
+ case 'Contract Accept Request':
+ return 'default'
+ case 'Complete the Contract':
+ return 'default'
+ case 'Reject to Accept Contract':
+ case 'Contract Delete':
+ return 'destructive'
+ default:
+ return 'outline'
+ }
+}
+
+// 상태 텍스트 변환
+const getStatusText = (status: string) => {
+ switch (status) {
+ case 'Draft':
+ return '임시저장'
+ case 'Request to Review':
+ return '조건검토요청'
+ case 'Confirm to Review':
+ return '조건검토완료'
+ case 'Contract Accept Request':
+ return '계약승인요청'
+ case 'Complete the Contract':
+ return '계약체결'
+ case 'Reject to Accept Contract':
+ return '계약승인거절'
+ case 'Contract Delete':
+ return '계약폐기'
+ case 'PCR Request':
+ return 'PCR요청'
+ case 'VO Request':
+ return 'VO요청'
+ case 'PCR Accept':
+ return 'PCR승인'
+ case 'PCR Reject':
+ return 'PCR거절'
+ default:
+ return status
+ }
+}
+
+// 계약구분 텍스트 변환
+const getCategoryText = (category: string) => {
+ switch (category) {
+ case 'unit_price':
+ return '단가계약'
+ case 'general':
+ return '일반계약'
+ case 'sale':
+ return '매각계약'
+ default:
+ return category
+ }
+}
+
+// 계약종류 텍스트 변환
+const getTypeText = (type: string) => {
+ switch (type) {
+ case 'UP':
+ return '자재단가계약'
+ case 'LE':
+ return '임대차계약'
+ case 'IL':
+ return '개별운송계약'
+ case 'AL':
+ return '연간운송계약'
+ case 'OS':
+ return '외주용역계약'
+ case 'OW':
+ return '도급계약'
+ case 'IS':
+ return '검사계약'
+ case 'LO':
+ return 'LOI'
+ case 'FA':
+ return 'FA'
+ case 'SC':
+ return '납품합의계약'
+ case 'OF':
+ return '클레임상계계약'
+ case 'AW':
+ return '사전작업합의'
+ case 'AD':
+ return '사전납품합의'
+ case 'AM':
+ return '설계계약'
+ case 'SC_SELL':
+ return '폐기물매각계약'
+ default:
+ return type
+ }
+}
+
+// 체결방식 텍스트 변환
+const getExecutionMethodText = (method: string) => {
+ switch (method) {
+ case '전자계약':
+ return '전자계약'
+ case '오프라인계약':
+ return '오프라인계약'
+ default:
+ return method
+ }
+}
+
+// 업체선정방법 텍스트 변환
+const getcontractSourceTypeText = (method?: string) => {
+ if (!method) return '-'
+ switch (method) {
+ case 'estimate':
+ return '견적'
+ case 'bid':
+ return '입찰'
+ case 'manual':
+ return '자체생성'
+ default:
+ return method
+ }
+}
+
+// 금액 포맷팅
+const formatCurrency = (amount: string | number | null | undefined, currency = 'KRW') => {
+ if (!amount && amount !== 0) return '-'
+
+ const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount
+ if (isNaN(numAmount)) return '-'
+
+ // 통화 코드가 null이거나 유효하지 않은 경우 기본값 사용
+ const safeCurrency = currency && typeof currency === 'string' ? currency : 'USD'
+
+ return new Intl.NumberFormat('ko-KR', {
+ style: 'currency',
+ currency: safeCurrency,
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ }).format(numAmount)
+}
+
+export function getGeneralContractsColumns({ setRowAction }: GetColumnsProps): ColumnDef<GeneralContractListItem>[] {
+ return [
+ // ═══════════════════════════════════════════════════════════════
+ // 선택 및 기본 정보
+ // ═══════════════════════════════════════════════════════════════
+ {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")}
+ onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)}
+ aria-label="select all"
+ className="translate-y-0.5"
+ />
+ ),
+ cell: ({ row }) => (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(v) => row.toggleSelected(!!v)}
+ aria-label="select row"
+ className="translate-y-0.5"
+ />
+ ),
+ size: 40,
+ enableSorting: false,
+ enableHiding: false,
+ },
+
+ // ░░░ 계약번호 ░░░
+ {
+ accessorKey: "contractNumber",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약번호 (Rev.)" />,
+ cell: ({ row }) => (
+ <div className="font-mono text-sm">
+ {row.original.contractNumber}
+ {row.original.revision > 0 && (
+ <span className="ml-1 text-xs text-muted-foreground">
+ Rev.{row.original.revision}
+ </span>
+ )}
+ </div>
+ ),
+ size: 150,
+ meta: { excelHeader: "계약번호 (Rev.)" },
+ },
+
+ // ░░░ 계약상태 ░░░
+ {
+ accessorKey: "status",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약상태" />,
+ cell: ({ row }) => (
+ <Badge variant={getStatusBadgeVariant(row.original.status)}>
+ {getStatusText(row.original.status)}
+ </Badge>
+ ),
+ size: 120,
+ meta: { excelHeader: "계약상태" },
+ },
+
+ // ░░░ 계약명 ░░░
+ {
+ accessorKey: "name",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약명" />,
+ cell: ({ row }) => (
+ <div className="truncate max-w-[200px]" title={row.original.name}>
+ <Button
+ variant="link"
+ className="p-0 h-auto text-left justify-start"
+ onClick={() => setRowAction({ row, type: "view" })}
+ >
+ {row.original.name}
+ </Button>
+ </div>
+ ),
+ size: 200,
+ meta: { excelHeader: "계약명" },
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 계약 정보
+ // ═══════════════════════════════════════════════════════════════
+ {
+ header: "계약 정보",
+ columns: [
+ {
+ accessorKey: "category",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약구분" />,
+ cell: ({ row }) => (
+ <Badge variant="outline">
+ {getCategoryText(row.original.category)}
+ </Badge>
+ ),
+ size: 100,
+ meta: { excelHeader: "계약구분" },
+ },
+
+ {
+ accessorKey: "type",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약종류" />,
+ cell: ({ row }) => (
+ <Badge variant="secondary">
+ {getTypeText(row.original.type)}
+ </Badge>
+ ),
+ size: 120,
+ meta: { excelHeader: "계약종류" },
+ },
+
+ {
+ accessorKey: "executionMethod",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="체결방식" />,
+ cell: ({ row }) => (
+ <Badge variant="outline">
+ {getExecutionMethodText(row.original.executionMethod)}
+ </Badge>
+ ),
+ size: 100,
+ meta: { excelHeader: "체결방식" },
+ },
+
+ {
+ accessorKey: "contractSourceType",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="업체선정방법" />,
+ cell: ({ row }) => (
+ <Badge variant="outline">
+ {getcontractSourceTypeText(row.original.contractSourceType)}
+ </Badge>
+ ),
+ size: 200,
+ meta: { excelHeader: "업체선정방법" },
+ },
+ ]
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 협력업체 정보
+ // ═══════════════════════════════════════════════════════════════
+ {
+ header: "협력업체",
+ columns: [
+ {
+ accessorKey: "vendorName",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="협력업체명" />,
+ cell: ({ row }) => (
+ <div className="flex flex-col">
+ <span className="font-medium">{row.original.vendorName || '-'}</span>
+ <span className="text-xs text-muted-foreground">
+ {row.original.vendorCode ? row.original.vendorCode : "-"}
+ </span>
+ </div>
+ ),
+ size: 150,
+ meta: { excelHeader: "협력업체명" },
+ },
+
+ {
+ accessorKey: "projectName",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="프로젝트명" />,
+ cell: ({ row }) => (
+ <div className="flex flex-col">
+ <span className="font-medium">{row.original.projectName || '-'}</span>
+ <span className="text-xs text-muted-foreground">
+ {row.original.projectCode ? row.original.projectCode : "-"}
+ </span>
+ </div>
+ ),
+ size: 150,
+ meta: { excelHeader: "프로젝트명" },
+ },
+
+ ]
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 기간 정보
+ // ═══════════════════════════════════════════════════════════════
+ {
+ header: "계약기간",
+ columns: [
+ {
+ id: "contractPeriod",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약기간" />,
+ cell: ({ row }) => {
+ const startDate = row.original.startDate
+ const endDate = row.original.endDate
+
+ if (!startDate || !endDate) return <span className="text-muted-foreground">-</span>
+
+ const now = new Date()
+ const isActive = now >= new Date(startDate) && now <= new Date(endDate)
+ const isExpired = now > new Date(endDate)
+
+ return (
+ <div className="text-xs">
+ <div className={`${isActive ? 'text-green-600 font-medium' : isExpired ? 'text-red-600' : 'text-gray-600'}`}>
+ {formatDate(startDate, "KR")} ~ {formatDate(endDate, "KR")}
+ </div>
+ {isActive && (
+ <Badge variant="default" className="text-xs mt-1">진행중</Badge>
+ )}
+ {isExpired && (
+ <Badge variant="destructive" className="text-xs mt-1">만료</Badge>
+ )}
+ </div>
+ )
+ },
+ size: 200,
+ meta: { excelHeader: "계약기간" },
+ },
+ ]
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 금액 정보
+ // ═══════════════════════════════════════════════════════════════
+ {
+ header: "금액 정보",
+ columns: [
+ {
+ accessorKey: "currency",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="통화" />,
+ cell: ({ row }) => (
+ <span className="font-mono text-sm">{row.original.currency || 'KRW'}</span>
+ ),
+ size: 60,
+ meta: { excelHeader: "통화" },
+ },
+
+ {
+ accessorKey: "contractAmount",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약금액" />,
+ cell: ({ row }) => (
+ <span className="text-sm font-medium">
+ {formatCurrency(row.original.contractAmount, row.original.currency)}
+ </span>
+ ),
+ size: 200,
+ meta: { excelHeader: "계약금액" },
+ },
+ ]
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 담당자 및 관리 정보
+ // ═══════════════════════════════════════════════════════════════
+ {
+ header: "관리 정보",
+ columns: [
+ {
+ accessorKey: "managerName",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약담당자" />,
+ cell: ({ row }) => (
+ <div className="truncate max-w-[100px]" title={row.original.managerName || ''}>
+ {row.original.managerName || '-'}
+ </div>
+ ),
+ size: 100,
+ meta: { excelHeader: "계약담당자" },
+ },
+
+ {
+ accessorKey: "registeredAt",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약등록일" />,
+ cell: ({ row }) => (
+ <span className="text-sm">{formatDate(row.original.registeredAt, "KR")}</span>
+ ),
+ size: 100,
+ meta: { excelHeader: "계약등록일" },
+ },
+
+ {
+ accessorKey: "signedAt",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약체결일" />,
+ cell: ({ row }) => (
+ <span className="text-sm">
+ {row.original.signedAt ? formatDate(row.original.signedAt, "KR") : '-'}
+ </span>
+ ),
+ size: 100,
+ meta: { excelHeader: "계약체결일" },
+ },
+
+ {
+ accessorKey: "linkedPoNumber",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="연계 PO번호" />,
+ cell: ({ row }) => (
+ <span className="font-mono text-sm">{row.original.linkedPoNumber || '-'}</span>
+ ),
+ size: 140,
+ meta: { excelHeader: "연계 PO번호" },
+ },
+
+ {
+ accessorKey: "lastUpdatedAt",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="최종수정일" />,
+ cell: ({ row }) => (
+ <span className="text-sm">{formatDate(row.original.lastUpdatedAt, "KR")}</span>
+ ),
+ size: 100,
+ meta: { excelHeader: "최종수정일" },
+ },
+
+ {
+ accessorKey: "lastUpdatedByName",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="최종수정자" />,
+ cell: ({ row }) => (
+ <span className="text-sm">{row.original.lastUpdatedByName || '-'}</span>
+ ),
+ size: 100,
+ meta: { excelHeader: "최종수정자" },
+ },
+ ]
+ },
+
+ // ░░░ 비고 ░░░
+ {
+ accessorKey: "notes",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="비고" />,
+ cell: ({ row }) => (
+ <div className="truncate max-w-[150px]" title={row.original.notes || ''}>
+ {row.original.notes || '-'}
+ </div>
+ ),
+ size: 150,
+ meta: { excelHeader: "비고" },
+ },
+
+ // ═══════════════════════════════════════════════════════════════
+ // 액션
+ // ═══════════════════════════════════════════════════════════════
+ {
+ id: "actions",
+ header: "액션",
+ cell: ({ row }) => (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" className="h-8 w-8 p-0">
+ <span className="sr-only">메뉴 열기</span>
+ <MoreHorizontal className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ {row.original.status !== 'Contract Delete' && (
+ <>
+ <DropdownMenuItem onClick={() => setRowAction({ row, type: "view" })}>
+ <Eye className="mr-2 h-4 w-4" />
+ 상세보기
+ </DropdownMenuItem>
+ <DropdownMenuItem onClick={() => setRowAction({ row, type: "update" })}>
+ <Edit className="mr-2 h-4 w-4" />
+ 수정
+ </DropdownMenuItem>
+ </>
+ )}
+ </DropdownMenuContent>
+ </DropdownMenu>
+ ),
+ size: 50,
+ enableSorting: false,
+ enableHiding: false,
+ },
+ ]
+}
diff --git a/lib/general-contracts_old/main/general-contracts-table-toolbar-actions.tsx b/lib/general-contracts_old/main/general-contracts-table-toolbar-actions.tsx
new file mode 100644
index 00000000..f16b759a
--- /dev/null
+++ b/lib/general-contracts_old/main/general-contracts-table-toolbar-actions.tsx
@@ -0,0 +1,124 @@
+"use client"
+
+import * as React from "react"
+import { type Table } from "@tanstack/react-table"
+import {
+ Download, FileSpreadsheet,
+ Trash2,
+} from "lucide-react"
+import { deleteContract } from "../service"
+import { toast } from "sonner"
+import { exportTableToExcel } from "@/lib/export"
+import { Button } from "@/components/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { GeneralContractListItem } from "./general-contracts-table-columns"
+import { CreateGeneralContractDialog } from "./create-general-contract-dialog"
+
+interface GeneralContractsTableToolbarActionsProps {
+ table: Table<GeneralContractListItem>
+}
+
+export function GeneralContractsTableToolbarActions({ table }: GeneralContractsTableToolbarActionsProps) {
+ const [isExporting, setIsExporting] = React.useState(false)
+
+ // 선택된 계약들
+ const selectedContracts = React.useMemo(() => {
+ return table
+ .getFilteredSelectedRowModel()
+ .rows
+ .map(row => row.original)
+ }, [table.getFilteredSelectedRowModel().rows])
+
+ const handleExport = async () => {
+ try {
+ setIsExporting(true)
+ await exportTableToExcel(table, {
+ filename: "general-contracts",
+ excludeColumns: ["select", "actions"],
+ })
+ toast.success("계약 목록이 성공적으로 내보내졌습니다.")
+ } catch (error) {
+ toast.error("내보내기 중 오류가 발생했습니다.")
+ } finally {
+ setIsExporting(false)
+ }
+ }
+
+
+ const handleDelete = async () => {
+ if (selectedContracts.length === 0) {
+ toast.error("계약폐기할 계약을 선택해주세요.")
+ return
+ }
+
+ // // 계약폐기 확인
+ // const confirmed = window.confirm(
+ // `선택한 ${selectedContracts.length}개 계약을 폐기하시겠습니까?\n계약폐기 후에는 복구할 수 없습니다.`
+ // )
+
+ // if (!confirmed) return
+
+ try {
+ // 선택된 모든 계약을 폐기 처리
+ const deletePromises = selectedContracts.map(contract =>
+ deleteContract(contract.id)
+ )
+
+ await Promise.all(deletePromises)
+
+ toast.success(`${selectedContracts.length}개 계약이 폐기되었습니다.`)
+
+ // 테이블 새로고침
+ } catch (error) {
+ console.error('Error deleting contracts:', error)
+ toast.error("계약폐기 중 오류가 발생했습니다.")
+ }
+ }
+
+ return (
+ <div className="flex items-center gap-2">
+ {/* 신규 등록 */}
+ <CreateGeneralContractDialog />
+
+ {/* 계약폐기 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleDelete}
+ disabled={selectedContracts.length === 0}
+ className="text-red-600 hover:text-red-700 hover:bg-red-50"
+ >
+ <Trash2 className="mr-2 h-4 w-4" />
+ 계약폐기
+ </Button>
+
+ {/* Export */}
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ className="gap-2"
+ disabled={isExporting}
+ >
+ <Download className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">
+ {isExporting ? "내보내는 중..." : "Export"}
+ </span>
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ <DropdownMenuItem onClick={handleExport} disabled={isExporting}>
+ <FileSpreadsheet className="mr-2 size-4" />
+ <span>계약 목록 내보내기</span>
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </div>
+ )
+}
diff --git a/lib/general-contracts_old/main/general-contracts-table.tsx b/lib/general-contracts_old/main/general-contracts-table.tsx
new file mode 100644
index 00000000..e4c96ee3
--- /dev/null
+++ b/lib/general-contracts_old/main/general-contracts-table.tsx
@@ -0,0 +1,217 @@
+"use client"
+
+import * as React from "react"
+import { useRouter } from "next/navigation"
+import type {
+ DataTableAdvancedFilterField,
+ DataTableFilterField,
+ DataTableRowAction,
+} from "@/types/table"
+
+import { useDataTable } from "@/hooks/use-data-table"
+import { DataTable } from "@/components/data-table/data-table"
+import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
+import { getGeneralContractsColumns, GeneralContractListItem } from "./general-contracts-table-columns"
+import { getGeneralContracts, getGeneralContractStatusCounts } from "@/lib/general-contracts/service"
+import { GeneralContractsTableToolbarActions } from "./general-contracts-table-toolbar-actions"
+import { GeneralContractUpdateSheet } from "./general-contract-update-sheet"
+import {
+ GENERAL_EXECUTION_METHODS
+} from "@/lib/general-contracts/types"
+
+// 상태 라벨 매핑
+const contractStatusLabels = {
+ 'Draft': '임시저장',
+ 'Request to Review': '조건검토요청',
+ 'Confirm to Review': '조건검토완료',
+ 'Contract Accept Request': '계약승인요청',
+ 'Complete the Contract': '계약체결',
+ 'Reject to Accept Contract': '계약승인거절',
+ 'Contract Delete': '계약폐기',
+ 'PCR Request': 'PCR요청',
+ 'VO Request': 'VO요청',
+ 'PCR Accept': 'PCR승인',
+ 'PCR Reject': 'PCR거절'
+}
+
+// 계약구분 라벨 매핑
+const contractCategoryLabels = {
+ '단가계약': '단가계약',
+ '일반계약': '일반계약',
+ '매각계약': '매각계약'
+}
+
+// 계약종류 라벨 매핑
+const contractTypeLabels = {
+ 'UP': '자재단가계약',
+ 'LE': '임대차계약',
+ 'IL': '개별운송계약',
+ 'AL': '연간운송계약',
+ 'OS': '외주용역계약',
+ 'OW': '도급계약',
+ 'IS': '검사계약',
+ 'LO': 'LOI',
+ 'FA': 'FA',
+ 'SC': '납품합의계약',
+ 'OF': '클레임상계계약',
+ 'AW': '사전작업합의',
+ 'AD': '사전납품합의',
+ 'AM': '설계계약',
+ 'SC_SELL': '폐기물매각계약'
+}
+
+interface GeneralContractsTableProps {
+ promises: Promise<
+ [
+ Awaited<ReturnType<typeof getGeneralContracts>>,
+ Awaited<ReturnType<typeof getGeneralContractStatusCounts>>
+ ]
+ >
+}
+
+export function GeneralContractsTable({ promises }: GeneralContractsTableProps) {
+ const [{ data, pageCount }, statusCounts] = React.use(promises)
+ const [isCompact, setIsCompact] = React.useState<boolean>(false)
+ const [rowAction, setRowAction] = React.useState<DataTableRowAction<GeneralContractListItem> | null>(null)
+ const [updateSheetOpen, setUpdateSheetOpen] = React.useState(false)
+ const [selectedContract, setSelectedContract] = React.useState<GeneralContractListItem | null>(null)
+
+ console.log(data, "data")
+
+ const router = useRouter()
+
+ const columns = React.useMemo(
+ () => getGeneralContractsColumns({ setRowAction }),
+ [setRowAction]
+ )
+
+ // rowAction 변경 감지하여 해당 액션 처리
+ React.useEffect(() => {
+ if (rowAction) {
+ setSelectedContract(rowAction.row.original)
+
+ switch (rowAction.type) {
+ case "view":
+ // 상세 페이지로 이동
+ router.push(`/evcp/general-contracts/${rowAction.row.original.id}`)
+ break
+ case "update":
+ // 수정 시트 열기
+ setSelectedContract(rowAction.row.original)
+ setUpdateSheetOpen(true)
+ break
+ default:
+ break
+ }
+ }
+ }, [rowAction, router])
+
+ const filterFields: DataTableFilterField<GeneralContractListItem>[] = []
+
+ const advancedFilterFields: DataTableAdvancedFilterField<GeneralContractListItem>[] = [
+ { id: "name", label: "계약명", type: "text" },
+ { id: "contractNumber", label: "계약번호", type: "text" },
+ { id: "vendorName", label: "협력업체명", type: "text" },
+ { id: "managerName", label: "계약담당자", type: "text" },
+ {
+ id: "status",
+ label: "계약상태",
+ type: "multi-select",
+ options: Object.entries(contractStatusLabels).map(([value, label]) => ({
+ label,
+ value,
+ count: statusCounts[value] || 0,
+ })),
+ },
+ {
+ id: "category",
+ label: "계약구분",
+ type: "select",
+ options: Object.entries(contractCategoryLabels).map(([value, label]) => ({
+ label,
+ value,
+ })),
+ },
+ {
+ id: "type",
+ label: "계약종류",
+ type: "select",
+ options: Object.entries(contractTypeLabels).map(([value, label]) => ({
+ label,
+ value,
+ })),
+ },
+ {
+ id: "executionMethod",
+ label: "체결방식",
+ type: "select",
+ options: GENERAL_EXECUTION_METHODS.map(value => ({
+ label: value,
+ value: value,
+ })),
+ },
+ {
+ id: "contractSourceType",
+ label: "업체선정방법",
+ type: "select",
+ options: [
+ { label: "estimate", value: "견적" },
+ { label: "bid", value: "입찰" },
+ { label: "manual", value: "자체생성" },
+ ],
+ },
+ { id: "registeredAt", label: "계약등록일", type: "date" },
+ { id: "signedAt", label: "계약체결일", type: "date" },
+ { id: "lastUpdatedAt", label: "최종수정일", type: "date" },
+ ]
+
+ const { table } = useDataTable({
+ data,
+ columns,
+ pageCount,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState: {
+ sorting: [{ id: "registeredAt", desc: true }],
+ columnPinning: { right: ["actions"] },
+ },
+ getRowId: (originalRow) => String(originalRow.id),
+ shallow: false,
+ clearOnDefault: true,
+ })
+
+ const handleCompactChange = React.useCallback((compact: boolean) => {
+ setIsCompact(compact)
+ }, [])
+
+ return (
+ <>
+ <DataTable
+ table={table}
+ compact={isCompact}
+ >
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ enableCompactToggle={true}
+ compactStorageKey="generalContractsTableCompact"
+ onCompactChange={handleCompactChange}
+ >
+ <GeneralContractsTableToolbarActions table={table} />
+ </DataTableAdvancedToolbar>
+ </DataTable>
+
+ <GeneralContractUpdateSheet
+ contract={selectedContract}
+ open={updateSheetOpen}
+ onOpenChange={setUpdateSheetOpen}
+ onSuccess={() => {
+ // 테이블 새로고침 또는 상태 업데이트
+ window.location.reload()
+ }}
+ />
+ </>
+ )
+}