summaryrefslogtreecommitdiff
path: root/lib/evaluation-target-list/table
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-10 09:55:45 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-10 09:55:45 +0000
commitc657ef972feeafff16ab0e07cb4771f7dd141ba0 (patch)
treebefabd884b00d3cc632c628b3e3810f61cc9f38d /lib/evaluation-target-list/table
parentb8a03c9d130435a71c5d6217d06ccb0beb9697e5 (diff)
(대표님) 20250710 작업사항 - 평가 첨부, 로그인, SEDP 변경 요구사항 반영
Diffstat (limited to 'lib/evaluation-target-list/table')
-rw-r--r--lib/evaluation-target-list/table/evaluation-target-table.tsx2
-rw-r--r--lib/evaluation-target-list/table/evaluation-targets-columns.tsx66
-rw-r--r--lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx181
-rw-r--r--lib/evaluation-target-list/table/update-evaluation-target.tsx10
4 files changed, 189 insertions, 70 deletions
diff --git a/lib/evaluation-target-list/table/evaluation-target-table.tsx b/lib/evaluation-target-list/table/evaluation-target-table.tsx
index b140df0e..5560d3ff 100644
--- a/lib/evaluation-target-list/table/evaluation-target-table.tsx
+++ b/lib/evaluation-target-list/table/evaluation-target-table.tsx
@@ -271,6 +271,8 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
const [promiseData] = React.use(promises);
const tableData = promiseData;
+ console.log(tableData)
+
/* ---------------------- 검색 파라미터 안전 처리 ---------------------- */
const searchString = React.useMemo(
() => searchParams.toString(), // query가 바뀔 때만 새로 계산
diff --git a/lib/evaluation-target-list/table/evaluation-targets-columns.tsx b/lib/evaluation-target-list/table/evaluation-targets-columns.tsx
index 60f1af39..c3aa9d71 100644
--- a/lib/evaluation-target-list/table/evaluation-targets-columns.tsx
+++ b/lib/evaluation-target-list/table/evaluation-targets-columns.tsx
@@ -16,7 +16,7 @@ interface GetColumnsProps {
}
// ✅ 모든 헬퍼 함수들을 컴포넌트 외부로 이동
-const getStatusBadgeVariant = (status: string) => {
+export const getStatusBadgeVariant = (status: string) => {
switch (status) {
case "PENDING": return "secondary";
case "CONFIRMED": return "default";
@@ -43,14 +43,14 @@ const getDivisionBadge = (division: string) => {
);
};
-const getMaterialTypeBadge = (materialType: string) => {
+export const getMaterialTypeBadge = (materialType: string) => {
return <Badge variant="outline">{vendortypeMap[materialType] || materialType}</Badge>;
};
const getDomesticForeignBadge = (domesticForeign: string) => {
return (
<Badge variant={domesticForeign === "DOMESTIC" ? "default" : "secondary"}>
- {domesticForeign === "DOMESTIC" ? "내자" : "외자"}
+ {domesticForeign === "DOMESTIC" ? "D" : "F"}
</Badge>
);
};
@@ -72,6 +72,16 @@ const getEvaluationTargetBadge = (isTarget: boolean | null) => {
);
};
+const getStatusLabel = (status: string) => {
+ const statusMap = {
+ PENDING: "미확정",
+ EXCLUDED: "제외",
+ CONFIRMED: "확정"
+ };
+ return statusMap[status] || status;
+};
+
+
// ✅ 모든 cell 렌더러 함수들을 미리 정의 (매번 새로 생성 방지)
const renderEvaluationYear = ({ row }: any) => (
<span className="font-medium">{row.getValue("evaluationYear")}</span>
@@ -83,7 +93,7 @@ const renderStatus = ({ row }: any) => {
const status = row.getValue<string>("status");
return (
<Badge variant={getStatusBadgeVariant(status)}>
- {status}
+ {getStatusLabel(status)}
</Badge>
);
};
@@ -213,12 +223,7 @@ function createStaticColumns(setRowAction: GetColumnsProps['setRowAction']): Col
cell: renderStatus,
size: 100,
},
- {
- accessorKey: "consensusStatus",
- header: createHeaderRenderer("의견 일치"),
- cell: renderConsensusStatus,
- size: 100,
- },
+
// 벤더 정보
{
@@ -252,6 +257,47 @@ function createStaticColumns(setRowAction: GetColumnsProps['setRowAction']): Col
]
},
+ {
+ accessorKey: "consensusStatus",
+ header: createHeaderRenderer("의견 일치"),
+ cell: renderConsensusStatus,
+ size: 100,
+ },
+
+ {
+ id: "claim",
+ header: "L/D, Claim",
+ columns:[
+ {
+ accessorKey: "ldClaimCount",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="건수" />,
+ cell: ({ row }) => (
+ <span className="text-sm">{row.original.ldClaimCount}</span>
+ ),
+ size: 80,
+ },
+
+ {
+ accessorKey: "ldClaimAmount",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="금액" />,
+ cell: ({ row }) => (
+ <span className="font-mono text-sm">{(Number(row.original.ldClaimAmount).toLocaleString())}</span>
+ ),
+ size: 80,
+ },
+ {
+ accessorKey: "ldClaimCurrency",
+ header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="단위" />,
+
+ cell: ({ row }) => (
+ <span className="text-sm">{row.original.ldClaimCurrency}</span>
+ ),
+ size: 80,
+ },
+ ]
+
+ },
+
// 발주 담당자
{
id: "orderReviewer",
diff --git a/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx b/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
index 8bc5254c..d1c7e500 100644
--- a/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
+++ b/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
@@ -14,6 +14,7 @@ import {
} from "lucide-react"
import { toast } from "sonner"
import { useRouter } from "next/navigation"
+import { useSession } from "next-auth/react"
import { Button } from "@/components/ui/button"
import {
@@ -31,6 +32,8 @@ import {
} from "./evaluation-target-action-dialogs"
import { EvaluationTargetWithDepartments } from "@/db/schema"
import { exportTableToExcel } from "@/lib/export"
+import { autoGenerateEvaluationTargets } from "../service" // 서버 액션 import
+import { useAuthRole } from "@/hooks/use-auth-role"
interface EvaluationTargetsTableToolbarActionsProps {
table: Table<EvaluationTargetWithDepartments>
@@ -47,6 +50,16 @@ export function EvaluationTargetsTableToolbarActions({
const [excludeDialogOpen, setExcludeDialogOpen] = React.useState(false)
const [reviewDialogOpen, setReviewDialogOpen] = React.useState(false)
const router = useRouter()
+ const { data: session } = useSession()
+
+ // 권한 체크
+ const { hasRole, isLoading: roleLoading } = useAuthRole()
+ const canManageEvaluations = hasRole('정기평가') || hasRole('admin')
+
+ // 사용자 ID 가져오기
+ const userId = React.useMemo(() => {
+ return session?.user?.id ? Number(session.user.id) : 1;
+ }, [session]);
// 선택된 행들
const selectedRows = table.getFilteredSelectedRowModel().rows
@@ -141,16 +154,36 @@ export function EvaluationTargetsTableToolbarActions({
const handleAutoGenerate = React.useCallback(async () => {
setIsLoading(true)
try {
- // TODO: 발주실적에서 자동 추출 API 호출
- toast.success("평가 대상이 자동으로 생성되었습니다.")
- router.refresh()
+ // 현재 년도를 기준으로 평가 대상 자동 생성
+ const currentYear = new Date().getFullYear()
+ const result = await autoGenerateEvaluationTargets(currentYear, userId)
+
+ if (result.success) {
+ if (result.generatedCount === 0) {
+ toast.info(result.message, {
+ description: result.skippedCount
+ ? `이미 존재하는 평가 대상: ${result.skippedCount}개`
+ : undefined
+ })
+ } else {
+ toast.success(result.message, {
+ description: result.details
+ ? `해양: ${result.details.shipTargets}개, 조선: ${result.details.plantTargets}개 생성${result.details.duplicateSkipped > 0 ? `, 중복 건너뜀: ${result.details.duplicateSkipped}개` : ''}`
+ : undefined
+ })
+ }
+ onRefresh?.()
+ router.refresh()
+ } else {
+ toast.error(result.error || "자동 생성 중 오류가 발생했습니다.")
+ }
} catch (error) {
console.error('Error auto generating targets:', error)
toast.error("자동 생성 중 오류가 발생했습니다.")
} finally {
setIsLoading(false)
}
- }, [router])
+ }, [router, onRefresh, userId])
// ----------------------------------------------------------------
// 신규 평가 대상 생성 (수동)
@@ -178,33 +211,54 @@ export function EvaluationTargetsTableToolbarActions({
})
}, [table])
+ // 권한이 없거나 로딩 중인 경우 내보내기 버튼만 표시
+ if (roleLoading) {
+ return (
+ <div className="flex items-center gap-2">
+ <div className="flex items-center gap-1 border-l pl-2 ml-2">
+ <Button
+ variant="outline"
+ size="sm"
+ disabled
+ className="gap-2"
+ >
+ <Download className="size-4 animate-spin" aria-hidden="true" />
+ <span className="hidden sm:inline">로딩중...</span>
+ </Button>
+ </div>
+ </div>
+ )
+ }
+
return (
<>
<div className="flex items-center gap-2">
- {/* 신규 생성 드롭다운 */}
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- variant="default"
- size="sm"
- className="gap-2"
- disabled={isLoading}
- >
- <Plus className="size-4" aria-hidden="true" />
- <span className="hidden sm:inline">신규 생성</span>
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start">
- <DropdownMenuItem onClick={handleAutoGenerate} disabled={isLoading}>
- <RefreshCw className="size-4 mr-2" />
- 자동 생성 (발주실적 기반)
- </DropdownMenuItem>
- <DropdownMenuItem onClick={handleManualCreate}>
- <Plus className="size-4 mr-2" />
- 수동 생성
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
+ {/* 신규 생성 드롭다운 - 정기평가 권한이 있는 경우만 표시 */}
+ {canManageEvaluations && (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="default"
+ size="sm"
+ className="gap-2"
+ disabled={isLoading}
+ >
+ <Plus className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">신규 생성</span>
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="start">
+ <DropdownMenuItem onClick={handleAutoGenerate} disabled={isLoading}>
+ <RefreshCw className={`size-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
+ 자동 생성 (발주실적 기반)
+ </DropdownMenuItem>
+ <DropdownMenuItem onClick={handleManualCreate}>
+ <Plus className="size-4 mr-2" />
+ 수동 생성
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ )}
{/* 유틸리티 버튼들 */}
<div className="flex items-center gap-1 border-l pl-2 ml-2">
@@ -219,8 +273,8 @@ export function EvaluationTargetsTableToolbarActions({
</Button>
</div>
- {/* 선택된 항목 액션 버튼들 */}
- {hasSelection && (
+ {/* 선택된 항목 액션 버튼들 - 정기평가 권한이 있는 경우만 표시 */}
+ {canManageEvaluations && hasSelection && (
<div className="flex items-center gap-1 border-l pl-2 ml-2">
{/* 확정 버튼 */}
{selectedStats.canConfirm && (
@@ -271,37 +325,52 @@ export function EvaluationTargetsTableToolbarActions({
)}
</div>
)}
+
+ {/* 권한이 없는 경우 안내 메시지 (선택사항) */}
+ {!canManageEvaluations && hasSelection && (
+ <div className="flex items-center gap-1 border-l pl-2 ml-2">
+ <div className="text-xs text-muted-foreground px-2 py-1">
+ 평가 관리 권한이 필요합니다
+ </div>
+ </div>
+ )}
</div>
- {/* 수동 생성 다이얼로그 */}
- <ManualCreateEvaluationTargetDialog
- open={manualCreateDialogOpen}
- onOpenChange={setManualCreateDialogOpen}
- />
+ {/* 다이얼로그들 - 권한이 있는 경우만 렌더링 */}
+ {canManageEvaluations && (
+ <>
+ {/* 수동 생성 다이얼로그 */}
+ <ManualCreateEvaluationTargetDialog
+ open={manualCreateDialogOpen}
+ onOpenChange={setManualCreateDialogOpen}
+ onSuccess={handleActionSuccess}
+ />
- {/* 확정 컨펌 다이얼로그 */}
- <ConfirmTargetsDialog
- open={confirmDialogOpen}
- onOpenChange={setConfirmDialogOpen}
- targets={selectedTargets}
- onSuccess={handleActionSuccess}
- />
+ {/* 확정 컨펌 다이얼로그 */}
+ <ConfirmTargetsDialog
+ open={confirmDialogOpen}
+ onOpenChange={setConfirmDialogOpen}
+ targets={selectedTargets}
+ onSuccess={handleActionSuccess}
+ />
- {/* 제외 컨펌 다이얼로그 */}
- <ExcludeTargetsDialog
- open={excludeDialogOpen}
- onOpenChange={setExcludeDialogOpen}
- targets={selectedTargets}
- onSuccess={handleActionSuccess}
- />
+ {/* 제외 컨펌 다이얼로그 */}
+ <ExcludeTargetsDialog
+ open={excludeDialogOpen}
+ onOpenChange={setExcludeDialogOpen}
+ targets={selectedTargets}
+ onSuccess={handleActionSuccess}
+ />
- {/* 의견 요청 다이얼로그 */}
- <RequestReviewDialog
- open={reviewDialogOpen}
- onOpenChange={setReviewDialogOpen}
- targets={selectedTargets}
- onSuccess={handleActionSuccess}
- />
+ {/* 의견 요청 다이얼로그 */}
+ <RequestReviewDialog
+ open={reviewDialogOpen}
+ onOpenChange={setReviewDialogOpen}
+ targets={selectedTargets}
+ onSuccess={handleActionSuccess}
+ />
+ </>
+ )}
</>
)
} \ No newline at end of file
diff --git a/lib/evaluation-target-list/table/update-evaluation-target.tsx b/lib/evaluation-target-list/table/update-evaluation-target.tsx
index 9f9b7af4..8ea63a1a 100644
--- a/lib/evaluation-target-list/table/update-evaluation-target.tsx
+++ b/lib/evaluation-target-list/table/update-evaluation-target.tsx
@@ -58,6 +58,8 @@ import {
type UpdateEvaluationTargetInput,
} from "../service"
import { EvaluationTargetWithDepartments } from "@/db/schema"
+import { getMaterialTypeBadge } from "./evaluation-targets-columns"
+import { getStatusLabel } from "../validation"
// 편집 가능한 필드들에 대한 스키마
const editEvaluationTargetSchema = z.object({
@@ -123,10 +125,10 @@ export function EditEvaluationTargetSheet({
}
const userEmail = session.user.email
- const userRole = session.user.role
+ const userRole = session.user?.roles
// 평가관리자는 모든 권한
- if (userRole === "평가관리자") {
+ if (userRole?.some(role => role.includes('정기평가'))|| userRole?.some(role => role.toLocaleLowerCase().includes('admin'))) {
return {
level: "admin",
editableApprovals: [
@@ -372,10 +374,10 @@ export function EditEvaluationTargetSheet({
<span className="font-medium">벤더명:</span> {evaluationTarget.vendorName}
</div>
<div>
- <span className="font-medium">자재구분:</span> {evaluationTarget.materialType}
+ <span className="font-medium">자재구분:</span> {getMaterialTypeBadge(evaluationTarget.materialType)}
</div>
<div>
- <span className="font-medium">상태:</span> {evaluationTarget.status}
+ <span className="font-medium">상태:</span> {getStatusLabel(evaluationTarget.status)}
</div>
</div>
</CardContent>