diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-23 05:26:26 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-23 05:26:26 +0000 |
| commit | 0547ab2fe1701d84753d0e078bba718a79b07a0c (patch) | |
| tree | 56e46cfa2e93a43ceaed0a8467ae21e61e9b0ddc /app | |
| parent | 37c618b94902603701e1fe3df7f76d238285f066 (diff) | |
(최겸)기술영업 벤더 개발 초안(index 스키마 미포함 상태)
Diffstat (limited to 'app')
4 files changed, 256 insertions, 0 deletions
diff --git a/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/items/page.tsx b/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/items/page.tsx new file mode 100644 index 00000000..5ca4492e --- /dev/null +++ b/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/items/page.tsx @@ -0,0 +1,56 @@ +import { Separator } from "@/components/ui/separator" +import { getVendorItemsByType, findVendorById } from "@/lib/tech-vendors/service" +import { type SearchParams } from "@/types/table" +import { TechVendorItemsTable } from "@/lib/tech-vendors/items-table/item-table" +import { notFound } from "next/navigation" + +interface PageProps { + params: { + lng: string + id: string + } + searchParams: Promise<SearchParams> +} + +export default async function Page(props: PageProps) { + const resolvedParams = await props.params + const id = resolvedParams.id + const vendorId = Number(id) + + if (isNaN(vendorId)) { + notFound() + } + + const vendor = await findVendorById(vendorId) + if (!vendor) { + notFound() + } + + const items = await getVendorItemsByType(vendorId, vendor.techVendorType) + + return ( + <div className="space-y-6"> + <div> + <h3 className="text-lg font-medium"> + Possible Items + </h3> + <p className="text-sm text-muted-foreground"> + 딜리버리가 가능한 아이템 리스트를 확인할 수 있습니다. + </p> + </div> + <Separator /> + <div> + <TechVendorItemsTable + data={items.data.map(item => ({ + ...item, + vendorId, + itemName: item.itemCode, + vendorItemId: item.id + }))} + vendorId={vendorId} + vendorType={vendor.techVendorType} + /> + </div> + </div> + ) +}
\ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/layout.tsx b/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/layout.tsx new file mode 100644 index 00000000..508ae82a --- /dev/null +++ b/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/layout.tsx @@ -0,0 +1,73 @@ +import { Metadata } from "next" + +import { Separator } from "@/components/ui/separator" +import { SidebarNav } from "@/components/layout/sidebar-nav" +import { findVendorById } from "@/lib/tech-vendors/service" +import { TechVendor } from "@/db/schema/techVendors" +import { Button } from "@/components/ui/button" +import { ArrowLeft } from "lucide-react" +import Link from "next/link" + +export const metadata: Metadata = { + title: "Tech Vendor Detail", +} + +export default async function SettingsLayout({ + children, + params, +}: { + children: React.ReactNode + params: { lng: string, id: string } +}) { + const resolvedParams = await params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + const vendor: TechVendor | null = await findVendorById(idAsNumber) + + const sidebarNavItems = [ + { + title: "연락처", + href: `/${lng}/evcp/tech-vendors/${id}/info`, + }, + { + title: "공급품목", + href: `/${lng}/evcp/tech-vendors/${id}/info/items`, + }, + ] + + return ( + <> + <div className="container py-6"> + <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow"> + <div className="hidden space-y-6 p-10 pb-16 md:block"> + <div className="flex items-center justify-end mb-4"> + <Link href={`/${lng}/evcp/tech-vendors`} passHref> + <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto"> + <ArrowLeft className="mr-1 h-4 w-4" /> + <span>기술협력업체 목록으로 돌아가기</span> + </Button> + </Link> + </div> + <div className="space-y-0.5"> + <h2 className="text-2xl font-bold tracking-tight"> + {vendor + ? `${vendor.vendorCode ?? ""} - ${vendor.vendorName} 상세 정보` + : "Loading Vendor..."} + </h2> + <p className="text-muted-foreground">기술협력업체 관련 상세사항을 확인하세요.</p> + </div> + <Separator className="my-6" /> + <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0"> + <aside className="-mx-4 lg:w-1/5"> + <SidebarNav items={sidebarNavItems} /> + </aside> + <div className="flex-1">{children}</div> + </div> + </div> + </section> + </div> + </> + ) +}
\ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/page.tsx b/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/page.tsx new file mode 100644 index 00000000..0092ee70 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/tech-vendors/[id]/info/page.tsx @@ -0,0 +1,56 @@ +import { Separator } from "@/components/ui/separator" +import { getTechVendorContacts } from "@/lib/tech-vendors/service" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { searchParamsContactCache } from "@/lib/tech-vendors/validations" +import { TechVendorContactsTable } from "@/lib/tech-vendors/contacts-table/contact-table" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise<SearchParams> +} + +export default async function SettingsAccountPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsContactCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + + + const promises = Promise.all([ + getTechVendorContacts({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + // 4) 렌더링 + return ( + <div className="space-y-6"> + <div> + <h3 className="text-lg font-medium"> + Contacts + </h3> + <p className="text-sm text-muted-foreground"> + 업무별 담당자 정보를 확인하세요. + </p> + </div> + <Separator /> + <div> + <TechVendorContactsTable promises={promises} vendorId={idAsNumber}/> + </div> + </div> + ) +}
\ 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 new file mode 100644 index 00000000..176a6fbc --- /dev/null +++ b/app/[lng]/evcp/(evcp)/tech-vendors/page.tsx @@ -0,0 +1,71 @@ +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 { 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 { Ellipsis } from "lucide-react" + +interface IndexPageProps { + searchParams: Promise<SearchParams> +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getTechVendors({ + ...search, + filters: validFilters, + }), + getTechVendorStatusCounts(), + ]) + + 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> + 을 통해 담당자 연락처, 공급 가능 아이템 등을 확인할 수 있습니다. <br/> + 벤더의 상태에 따라 가입을 승인해주거나 거부할 수 있습니다. + </p> + </div> + </div> + </div> + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + {/* 필요한 경우 데이터 범위 선택기 등의 추가 UI를 이곳에 배치할 수 있습니다 */} + </React.Suspense> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={6} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <TechVendorsTable promises={promises} /> + </React.Suspense> + </Shell> + ) +} |
