diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/evaluation-check-list/page.tsx | 63 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/tech-project-avl/page.tsx | 85 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/tech-vendor-candidates/page.tsx | 78 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/tech-vendors/page.tsx | 43 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/info/page.tsx | 12 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/techsales/rfq-offshore-hull/page.tsx | 96 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx | 93 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/techsales/rfq-ship/page.tsx | 98 | ||||
| -rw-r--r-- | app/[lng]/spreadTest/page.tsx | 17 | ||||
| -rw-r--r-- | app/layout.tsx | 2 |
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 |
