summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-06-24 01:51:59 +0000
committerjoonhoekim <26rote@gmail.com>2025-06-24 01:51:59 +0000
commit6824e097d768f724cf439b410ccfb1ab9685ac98 (patch)
tree1f297313637878e7a4ad6c89b84d5a2c3e9eb650 /app
parentf4825dd3853188de4688fb4a56c0f4e847da314b (diff)
parent4e63d8427d26d0d1b366ddc53650e15f3481fc75 (diff)
(merge) 대표님/최겸 작업사항 머지
Diffstat (limited to 'app')
-rw-r--r--app/[lng]/evcp/(evcp)/evaluation-check-list/page.tsx63
-rw-r--r--app/[lng]/evcp/(evcp)/tech-project-avl/page.tsx85
-rw-r--r--app/[lng]/evcp/(evcp)/tech-vendor-candidates/page.tsx78
-rw-r--r--app/[lng]/evcp/(evcp)/tech-vendors/page.tsx43
-rw-r--r--app/[lng]/partners/(partners)/info/page.tsx12
-rw-r--r--app/[lng]/partners/(partners)/techsales/rfq-offshore-hull/page.tsx96
-rw-r--r--app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx93
-rw-r--r--app/[lng]/partners/(partners)/techsales/rfq-ship/page.tsx98
-rw-r--r--app/[lng]/spreadTest/page.tsx17
-rw-r--r--app/layout.tsx2
10 files changed, 269 insertions, 318 deletions
diff --git a/app/[lng]/evcp/(evcp)/evaluation-check-list/page.tsx b/app/[lng]/evcp/(evcp)/evaluation-check-list/page.tsx
new file mode 100644
index 00000000..398005fa
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/evaluation-check-list/page.tsx
@@ -0,0 +1,63 @@
+/* IMPORT */
+import { DataTableSkeleton } from '@/components/data-table/data-table-skeleton';
+import { getRegEvalCriteria } from '@/lib/evaluation-criteria/service';
+import { getValidFilters } from '@/lib/data-table';
+import RegEvalCriteriaTable from '@/lib/evaluation-criteria/table/reg-eval-criteria-table';
+import { searchParamsCache } from '@/lib/evaluation-criteria/validations';
+import { Shell } from '@/components/shell';
+import { Skeleton } from '@/components/ui/skeleton';
+import { Suspense } from 'react';
+import { type SearchParams } from '@/types/table';
+
+// ----------------------------------------------------------------------------------------------------
+
+/* TYPES */
+interface EvaluationCriteriaPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/* REGULAR EVALUATION CRITERIA PAGE */
+async function EvaluationCriteriaPage(props: EvaluationCriteriaPageProps) {
+ const searchParams = await props.searchParams;
+ const search = searchParamsCache.parse(searchParams);
+ const validFilters = getValidFilters(search.filters);
+ const promises = Promise.all([
+ getRegEvalCriteria({
+ ...search,
+ filters: validFilters,
+ }),
+ ]);
+
+ return (
+ <Shell className="gap-2">
+ <Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </Suspense>
+ <Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={11}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <RegEvalCriteriaTable promises={promises} />
+ </Suspense>
+ </Shell>
+ )
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/* EXPORT */
+export default EvaluationCriteriaPage; \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/tech-project-avl/page.tsx b/app/[lng]/evcp/(evcp)/tech-project-avl/page.tsx
new file mode 100644
index 00000000..d942c5c5
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/tech-project-avl/page.tsx
@@ -0,0 +1,85 @@
+import * as React from "react"
+import { redirect } from "next/navigation"
+import { getServerSession } from "next-auth/next"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
+import { SearchParams } from "@/types/table"
+import { searchParamsCache } from "@/lib/tech-project-avl/validations"
+import { Skeleton } from "@/components/ui/skeleton"
+import { Shell } from "@/components/shell"
+import { AcceptedQuotationsTable } from "@/lib/tech-project-avl/table/accepted-quotations-table"
+import { getAcceptedTechSalesVendorQuotations } from "@/lib/techsales-rfq/service"
+import { getValidFilters } from "@/lib/data-table"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Ellipsis } from "lucide-react"
+
+export interface PageProps {
+ params: Promise<{ lng: string }>
+ searchParams: Promise<SearchParams>
+}
+
+export default async function AcceptedQuotationsPage({
+ params,
+ searchParams,
+}: PageProps) {
+ const { lng } = await params
+
+ const session = await getServerSession(authOptions)
+ if (!session) {
+ redirect(`/${lng}/auth/signin`)
+ }
+
+ const search = await searchParams
+ const { page, perPage, sort, filters, search: searchText } = searchParamsCache.parse(search)
+ const validFilters = getValidFilters(filters ?? [])
+
+ const { data, pageCount } = await getAcceptedTechSalesVendorQuotations({
+ page,
+ perPage: perPage ?? 10,
+ sort,
+ search: searchText,
+ filters: validFilters,
+ })
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 승인된 견적서(해양TOP,HULL)
+ </h2>
+ <p className="text-muted-foreground">
+ 기술영업 승인 견적서에 대한 요약 정보를 확인하고{" "}
+ <span className="inline-flex items-center whitespace-nowrap">
+ <Ellipsis className="size-3" />
+ <span className="ml-1">버튼</span>
+ </span>
+ 을 통해 RFQ 코드, 설명, 업체명, 업체 코드 등의 상세 정보를 확인할 수 있습니다.
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* Date range picker can be added here if needed */}
+ </React.Suspense>
+
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={12}
+ searchableColumnCount={2}
+ filterableColumnCount={4}
+ cellWidths={["10rem", "15rem", "12rem", "10rem", "10rem", "12rem", "8rem", "12rem", "10rem", "8rem", "10rem", "10rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <AcceptedQuotationsTable
+ data={data}
+ pageCount={pageCount}
+ />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/tech-vendor-candidates/page.tsx b/app/[lng]/evcp/(evcp)/tech-vendor-candidates/page.tsx
new file mode 100644
index 00000000..3923863a
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/tech-vendor-candidates/page.tsx
@@ -0,0 +1,78 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+
+import { getVendorCandidateCounts, getVendorCandidates } from "@/lib/tech-vendor-candidates/service"
+import { searchParamsTechCandidateCache } from "@/lib/tech-vendor-candidates/validations"
+import { VendorCandidateTable as TechVendorCandidateTable } from "@/lib/tech-vendor-candidates/table/candidates-table"
+import { DateRangePicker } from "@/components/date-range-picker"
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = searchParamsTechCandidateCache.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getVendorCandidates({
+ ...search,
+ filters: validFilters,
+ }),
+ getVendorCandidateCounts()
+ ])
+
+ return (
+ <Shell className="gap-2">
+
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ Vendor Candidates Management
+ </h2>
+ <p className="text-muted-foreground">
+ 수집한 협력업체 후보를 등록하고 초대 메일을 송부할 수 있습니다.
+ </p>
+ </div>
+ </div>
+ </div>
+
+ {/* 수집일 라벨과 DateRangePicker를 함께 배치 */}
+ <div className="flex items-center justify-start gap-2">
+ {/* <span className="text-sm font-medium">수집일 기간 설정: </span> */}
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ showClearButton={true}
+ placeholder="수집일 날짜 범위를 고르세요"
+ />
+ </React.Suspense>
+ </div>
+
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <TechVendorCandidateTable promises={promises}/>
+ </React.Suspense>
+ </Shell>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/tech-vendors/page.tsx b/app/[lng]/evcp/(evcp)/tech-vendors/page.tsx
index 64e8737f..8f542f59 100644
--- a/app/[lng]/evcp/(evcp)/tech-vendors/page.tsx
+++ b/app/[lng]/evcp/(evcp)/tech-vendors/page.tsx
@@ -2,14 +2,13 @@ import * as React from "react"
import { type SearchParams } from "@/types/table"
import { getValidFilters } from "@/lib/data-table"
-import { Skeleton } from "@/components/ui/skeleton"
import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
import { Shell } from "@/components/shell"
-import { Ellipsis } from "lucide-react"
import { searchParamsCache } from "@/lib/tech-vendors/validations"
import { getTechVendors, getTechVendorStatusCounts } from "@/lib/tech-vendors/service"
import { TechVendorsTable } from "@/lib/tech-vendors/table/tech-vendors-table"
+import { TechVendorContainer } from "@/components/tech-vendors/tech-vendor-container"
interface IndexPageProps {
searchParams: Promise<SearchParams>
@@ -21,6 +20,14 @@ export default async function IndexPage(props: IndexPageProps) {
const validFilters = getValidFilters(search.filters)
+ // 벤더 타입 정의
+ const vendorTypes = [
+ { id: "all", name: "전체", value: "" },
+ { id: "ship", name: "조선", value: "조선" },
+ { id: "top", name: "해양TOP", value: "해양TOP" },
+ { id: "hull", name: "해양HULL", value: "해양HULL" },
+ ]
+
const promises = Promise.all([
getTechVendors({
...search,
@@ -30,33 +37,7 @@ export default async function IndexPage(props: IndexPageProps) {
])
return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 기술영업 벤더 리스트
- </h2>
- <p className="text-muted-foreground">
- 기술영업 벤더에 대한 요약 정보를 확인하고{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
- triggerSize="sm"
- triggerClassName="ml-auto w-56 sm:w-60"
- align="end"
- shallow={false}
- /> */}
- </React.Suspense>
+ <Shell className="gap-4">
<React.Suspense
fallback={
<DataTableSkeleton
@@ -68,7 +49,9 @@ export default async function IndexPage(props: IndexPageProps) {
/>
}
>
- <TechVendorsTable promises={promises} />
+ <TechVendorContainer vendorTypes={vendorTypes}>
+ <TechVendorsTable promises={promises} />
+ </TechVendorContainer>
</React.Suspense>
</Shell>
)
diff --git a/app/[lng]/partners/(partners)/info/page.tsx b/app/[lng]/partners/(partners)/info/page.tsx
index 8215a451..cc1252e6 100644
--- a/app/[lng]/partners/(partners)/info/page.tsx
+++ b/app/[lng]/partners/(partners)/info/page.tsx
@@ -1,7 +1,10 @@
import { Suspense } from "react"
import { Metadata } from "next"
+import { getServerSession } from "next-auth/next"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
import { JoinFormSkeleton } from "@/components/signup/join-form-skeleton"
import { InfoForm } from "@/components/additional-info/join-form"
+import { TechVendorInfoForm } from "@/components/additional-info/tech-vendor-info-form"
// (Optional) If Next.js attempts to statically optimize this page and you need full runtime
// behavior for query params, you may also need:
@@ -12,10 +15,15 @@ export const metadata: Metadata = {
description: "Authentication forms built using the components.",
}
-export default function IndexPage() {
+export default async function IndexPage() {
+ const session = await getServerSession(authOptions)
+
+ // 사용자의 techCompanyId가 있는지 확인
+ const isTechVendor = session?.user?.techCompanyId
+
return (
<Suspense fallback={<JoinFormSkeleton/>}>
- <InfoForm />
+ {isTechVendor ? <TechVendorInfoForm /> : <InfoForm />}
</Suspense>
)
} \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/techsales/rfq-offshore-hull/page.tsx b/app/[lng]/partners/(partners)/techsales/rfq-offshore-hull/page.tsx
index 1c830535..5506825d 100644
--- a/app/[lng]/partners/(partners)/techsales/rfq-offshore-hull/page.tsx
+++ b/app/[lng]/partners/(partners)/techsales/rfq-offshore-hull/page.tsx
@@ -3,16 +3,9 @@ import Link from "next/link";
import { Metadata } from "next";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { LogIn } from "lucide-react";
import { Shell } from "@/components/shell";
-import {
- TECH_SALES_QUOTATION_STATUSES,
- TECH_SALES_QUOTATION_STATUS_CONFIG
-} from "@/db/schema";
-
-import { getQuotationStatusCounts } from "@/lib/techsales-rfq/service";
import { VendorQuotationsTable } from "@/lib/techsales-rfq/vendor-response/table/vendor-quotations-table";
export const metadata: Metadata = {
@@ -62,9 +55,6 @@ export default async function VendorQuotationsHullPage() {
);
}
- // 견적서 상태별 개수 조회
- const statusCountsPromise = getQuotationStatusCounts(vendorId.toString(), "HULL");
-
return (
<Shell variant="fullscreen" className="h-full">
{/* 고정 헤더 영역 */}
@@ -78,30 +68,6 @@ export default async function VendorQuotationsHullPage() {
</div>
</div>
- {/* 상태별 개수 카드 */}
- <div className="flex-shrink-0">
- <React.Suspense
- fallback={
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit">
- {Array.from({ length: 5 }).map((_, i) => (
- <Card key={i} className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">로딩중...</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="text-2xl font-bold">-</div>
- </CardContent>
- </Card>
- ))}
- </div>
- </div>
- }
- >
- <StatusCards statusCountsPromise={statusCountsPromise} />
- </React.Suspense>
- </div>
-
{/* 견적서 테이블 */}
<div className="flex-1 min-h-0 overflow-hidden">
<div className="h-full overflow-auto">
@@ -112,65 +78,3 @@ export default async function VendorQuotationsHullPage() {
</Shell>
);
}
-
-// 상태별 개수 카드 컴포넌트
-async function StatusCards({
- statusCountsPromise,
-}: {
- statusCountsPromise: Promise<{
- data: { status: string; count: number }[] | null;
- error: string | null;
- }>;
-}) {
- const { data: statusCounts, error } = await statusCountsPromise;
-
- if (error || !statusCounts) {
- return (
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 min-w-fit">
- <Card className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">오류</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="text-2xl font-bold text-red-600">-</div>
- <p className="text-xs text-muted-foreground truncate">
- 데이터를 불러올 수 없습니다
- </p>
- </CardContent>
- </Card>
- </div>
- </div>
- );
- }
-
- // 중앙화된 상태 설정 사용
- const statusEntries = Object.entries(TECH_SALES_QUOTATION_STATUSES).map(([, statusValue]) => ({
- key: statusValue,
- ...TECH_SALES_QUOTATION_STATUS_CONFIG[statusValue]
- }));
-
- console.log(statusCounts, "statusCounts")
-
- return (
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit">
- {statusEntries.map((status) => (
- <Card key={status.key} className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">{status.label}</CardTitle>
- </CardHeader>
- <CardContent>
- <div className={`text-2xl font-bold ${status.color}`}>
- {statusCounts.find(item => item.status === status.key)?.count || 0}
- </div>
- <p className="text-xs text-muted-foreground truncate">
- {status.description}
- </p>
- </CardContent>
- </Card>
- ))}
- </div>
- </div>
- );
-} \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx b/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx
index b9c957f0..408b5318 100644
--- a/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx
+++ b/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx
@@ -3,16 +3,10 @@ import Link from "next/link";
import { Metadata } from "next";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { LogIn } from "lucide-react";
import { Shell } from "@/components/shell";
-import {
- TECH_SALES_QUOTATION_STATUSES,
- TECH_SALES_QUOTATION_STATUS_CONFIG
-} from "@/db/schema";
-import { getQuotationStatusCounts } from "@/lib/techsales-rfq/service";
import { VendorQuotationsTable } from "@/lib/techsales-rfq/vendor-response/table/vendor-quotations-table";
export const metadata: Metadata = {
@@ -63,8 +57,6 @@ export default async function VendorQuotationsTopPage() {
}
// 견적서 상태별 개수 조회
- const statusCountsPromise = getQuotationStatusCounts(vendorId.toString(), "TOP");
-
return (
<Shell variant="fullscreen" className="h-full">
{/* 고정 헤더 영역 */}
@@ -78,29 +70,6 @@ export default async function VendorQuotationsTopPage() {
</div>
</div>
- {/* 상태별 개수 카드 */}
- <div className="flex-shrink-0">
- <React.Suspense
- fallback={
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit">
- {Array.from({ length: 5 }).map((_, i) => (
- <Card key={i} className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">로딩중...</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="text-2xl font-bold">-</div>
- </CardContent>
- </Card>
- ))}
- </div>
- </div>
- }
- >
- <StatusCards statusCountsPromise={statusCountsPromise} />
- </React.Suspense>
- </div>
{/* 견적서 테이블 */}
<div className="flex-1 min-h-0 overflow-hidden">
@@ -111,66 +80,4 @@ export default async function VendorQuotationsTopPage() {
</div>
</Shell>
);
-}
-
-// 상태별 개수 카드 컴포넌트
-async function StatusCards({
- statusCountsPromise,
-}: {
- statusCountsPromise: Promise<{
- data: { status: string; count: number }[] | null;
- error: string | null;
- }>;
-}) {
- const { data: statusCounts, error } = await statusCountsPromise;
-
- if (error || !statusCounts) {
- return (
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 min-w-fit">
- <Card className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">오류</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="text-2xl font-bold text-red-600">-</div>
- <p className="text-xs text-muted-foreground truncate">
- 데이터를 불러올 수 없습니다
- </p>
- </CardContent>
- </Card>
- </div>
- </div>
- );
- }
-
- // 중앙화된 상태 설정 사용
- const statusEntries = Object.entries(TECH_SALES_QUOTATION_STATUSES).map(([, statusValue]) => ({
- key: statusValue,
- ...TECH_SALES_QUOTATION_STATUS_CONFIG[statusValue]
- }));
-
- console.log(statusCounts, "statusCounts")
-
- return (
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit">
- {statusEntries.map((status) => (
- <Card key={status.key} className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">{status.label}</CardTitle>
- </CardHeader>
- <CardContent>
- <div className={`text-2xl font-bold ${status.color}`}>
- {statusCounts.find(item => item.status === status.key)?.count || 0}
- </div>
- <p className="text-xs text-muted-foreground truncate">
- {status.description}
- </p>
- </CardContent>
- </Card>
- ))}
- </div>
- </div>
- );
} \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/techsales/rfq-ship/page.tsx b/app/[lng]/partners/(partners)/techsales/rfq-ship/page.tsx
index 07797c9b..40c6bb1f 100644
--- a/app/[lng]/partners/(partners)/techsales/rfq-ship/page.tsx
+++ b/app/[lng]/partners/(partners)/techsales/rfq-ship/page.tsx
@@ -4,16 +4,9 @@ import Link from "next/link";
import { Metadata } from "next";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { LogIn } from "lucide-react";
import { Shell } from "@/components/shell";
-import {
- TECH_SALES_QUOTATION_STATUSES,
- TECH_SALES_QUOTATION_STATUS_CONFIG
-} from "@/db/schema";
-
-import { getQuotationStatusCounts } from "@/lib/techsales-rfq/service";
import { VendorQuotationsTable } from "@/lib/techsales-rfq/vendor-response/table/vendor-quotations-table";
export const metadata: Metadata = {
@@ -67,45 +60,19 @@ export default async function VendorQuotationsPage() {
}
// 견적서 상태별 개수 조회
- const statusCountsPromise = getQuotationStatusCounts(vendorId.toString(), "SHIP");
-
return (
<Shell variant="fullscreen" className="h-full">
{/* 고정 헤더 영역 */}
<div className="flex-shrink-0">
<div className="flex-shrink-0 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
- <h1 className="text-3xl font-bold tracking-tight">기술영업 견적서</h1>
+ <h1 className="text-3xl font-bold tracking-tight">기술영업 조선 견적서</h1>
<p className="text-muted-foreground">
- 할당받은 RFQ에 대한 견적서를 작성하고 관리합니다.
+ 할당받은 조선 RFQ에 대한 견적서를 작성하고 관리합니다.
</p>
</div>
</div>
- {/* 상태별 개수 카드 */}
- <div className="flex-shrink-0">
- <React.Suspense
- fallback={
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit">
- {Array.from({ length: 5 }).map((_, i) => (
- <Card key={i} className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">로딩중...</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="text-2xl font-bold">-</div>
- </CardContent>
- </Card>
- ))}
- </div>
- </div>
- }
- >
- <StatusCards statusCountsPromise={statusCountsPromise} />
- </React.Suspense>
- </div>
-
{/* 견적서 테이블 */}
<div className="flex-1 min-h-0 overflow-hidden">
<div className="h-full overflow-auto">
@@ -117,64 +84,3 @@ export default async function VendorQuotationsPage() {
);
}
-// 상태별 개수 카드 컴포넌트
-async function StatusCards({
- statusCountsPromise,
-}: {
- statusCountsPromise: Promise<{
- data: { status: string; count: number }[] | null;
- error: string | null;
- }>;
-}) {
- const { data: statusCounts, error } = await statusCountsPromise;
-
- if (error || !statusCounts) {
- return (
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 min-w-fit">
- <Card className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">오류</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="text-2xl font-bold text-red-600">-</div>
- <p className="text-xs text-muted-foreground truncate">
- 데이터를 불러올 수 없습니다
- </p>
- </CardContent>
- </Card>
- </div>
- </div>
- );
- }
-
- // 중앙화된 상태 설정 사용
- const statusEntries = Object.entries(TECH_SALES_QUOTATION_STATUSES).map(([, statusValue]) => ({
- key: statusValue,
- ...TECH_SALES_QUOTATION_STATUS_CONFIG[statusValue]
- }));
-
- console.log(statusCounts, "statusCounts")
-
- return (
- <div className="w-full overflow-x-auto">
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit">
- {statusEntries.map((status) => (
- <Card key={status.key} className="min-w-[160px]">
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
- <CardTitle className="text-sm font-medium truncate">{status.label}</CardTitle>
- </CardHeader>
- <CardContent>
- <div className={`text-2xl font-bold ${status.color}`}>
- {statusCounts.find(item => item.status === status.key)?.count || 0}
- </div>
- <p className="text-xs text-muted-foreground truncate">
- {status.description}
- </p>
- </CardContent>
- </Card>
- ))}
- </div>
- </div>
- );
-} \ No newline at end of file
diff --git a/app/[lng]/spreadTest/page.tsx b/app/[lng]/spreadTest/page.tsx
new file mode 100644
index 00000000..2e10eeb8
--- /dev/null
+++ b/app/[lng]/spreadTest/page.tsx
@@ -0,0 +1,17 @@
+'use client'
+
+
+import dynamic from "next/dynamic";
+
+const SpreadSheet = dynamic(
+ () => {
+ return import("@/components/spread-js/testSheet");
+ },
+ { ssr: false }
+);
+
+export default function SpreadTestPage() {
+ return (
+ <div className='w-[100vw] h-[100vh]'><SpreadSheet /></div>
+ )
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index c83c1a7e..4923aa22 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -74,7 +74,7 @@ export default async function RootLayout({
} catch (_) {}
`,
}}
- />
+ />
</head>
<body