diff options
Diffstat (limited to 'app')
6 files changed, 725 insertions, 0 deletions
diff --git a/app/[lng]/evcp/(evcp)/budgetary-tech-sales-ship/page.tsx b/app/[lng]/evcp/(evcp)/budgetary-tech-sales-ship/page.tsx new file mode 100644 index 00000000..05b856e5 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/budgetary-tech-sales-ship/page.tsx @@ -0,0 +1,61 @@ +import { searchParamsCache } from "@/lib/techsales-rfq/validations" +import { getTechSalesRfqsWithJoin } from "@/lib/techsales-rfq/service" +import { getValidFilters } from "@/lib/data-table" +import { Shell } from "@/components/shell" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { RFQListTable } from "@/lib/techsales-rfq/table/rfq-table" +import { type SearchParams } from "@/types/table" +import * as React from "react" + +interface RfqPageProps { + searchParams: Promise<SearchParams> +} + +export default async function RfqPage(props: RfqPageProps) { + // searchParams를 await하여 resolve + const searchParams = await props.searchParams + + // 파라미터 파싱 + const search = searchParamsCache.parse(searchParams); + const validFilters = getValidFilters(search.filters); + + // 기술영업 조선 RFQ 데이터를 Promise.all로 감싸서 전달 + const promises = Promise.all([ + getTechSalesRfqsWithJoin({ + ...search, // 모든 파라미터 전달 (page, perPage, sort, basicFilters, filters 등) + filters: validFilters, // 고급 필터를 명시적으로 오버라이드 (파싱된 버전) + }) + ]) + + return ( + <Shell variant="fullscreen" className="h-full"> {/* fullscreen variant 사용 */} + {/* 고정 헤더 영역 */} + <div className="flex-shrink-0"> + <div className="flex items-center justify-between"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + 기술영업-조선 RFQ + </h2> + </div> + </div> + </div> + + {/* 테이블 영역 - 남은 공간 모두 차지 */} + <div className="flex-1 min-h-0"> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={8} + searchableColumnCount={2} + filterableColumnCount={3} + cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]} + shrinkZero + /> + } + > + <RFQListTable promises={promises} className="h-full" /> + </React.Suspense> + </div> + </Shell> + ) +}
\ 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 new file mode 100644 index 00000000..40be6773 --- /dev/null +++ b/app/[lng]/partners/(partners)/techsales/rfq-offshore-hull/page.tsx @@ -0,0 +1,17 @@ +import { Shell } from "@/components/shell"; + +export default function TechSalesRfqShipPage() { + return ( + <Shell className="gap-6"> + <div> + <h1 className="text-2xl font-bold">기술영업 - 해양 Hull/Top RFQ</h1> + <p className="text-muted-foreground"> + 벤더가 해양 Hull/Top RFQ 목록을 확인하고 관리합니다. + </p> + <p className="text-muted-foreground"> + 기술영업 해양 Hull/Top 은 업무 요구사항이 동일하다면 통합으로 개발될 수 있습니다. + </p> + </div> + </Shell> + ); +}
\ 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 new file mode 100644 index 00000000..40be6773 --- /dev/null +++ b/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx @@ -0,0 +1,17 @@ +import { Shell } from "@/components/shell"; + +export default function TechSalesRfqShipPage() { + return ( + <Shell className="gap-6"> + <div> + <h1 className="text-2xl font-bold">기술영업 - 해양 Hull/Top RFQ</h1> + <p className="text-muted-foreground"> + 벤더가 해양 Hull/Top RFQ 목록을 확인하고 관리합니다. + </p> + <p className="text-muted-foreground"> + 기술영업 해양 Hull/Top 은 업무 요구사항이 동일하다면 통합으로 개발될 수 있습니다. + </p> + </div> + </Shell> + ); +}
\ No newline at end of file diff --git a/app/[lng]/partners/(partners)/techsales/rfq-ship/[id]/page.tsx b/app/[lng]/partners/(partners)/techsales/rfq-ship/[id]/page.tsx new file mode 100644 index 00000000..d748fc46 --- /dev/null +++ b/app/[lng]/partners/(partners)/techsales/rfq-ship/[id]/page.tsx @@ -0,0 +1,126 @@ +// app/[lng]/partners/(partners)/techsales/rfq-ship/[id]/page.tsx - 기술영업 RFQ 견적 응답 페이지 +import { Metadata } from "next" +import { notFound } from "next/navigation" +import * as React from "react" +import { Shell } from "@/components/shell" +import { Badge } from "@/components/ui/badge" +import { Separator } from "@/components/ui/separator" +import { BackButton } from "@/components/ui/back-button" +import { getTechSalesVendorQuotation } from "@/lib/techsales-rfq/service" +import { formatDate } from "@/lib/utils" +import { TechSalesQuotationTabs } from "@/lib/techsales-rfq/vendor-response/detail/quotation-tabs" + +interface TechSalesVendorQuotationDetailPageProps { + params: Promise<{ + id: string + lng: string + }> + searchParams: Promise<{ + tab?: string + }> +} + +export async function generateMetadata(): Promise<Metadata> { + return { + title: "기술영업 RFQ 견적서 상세", + description: "기술영업 RFQ 견적서 상세 정보 및 응답", + } +} + +export default async function TechSalesVendorQuotationDetailPage({ + params, + searchParams, +}: TechSalesVendorQuotationDetailPageProps) { + const { id } = await params + const { tab } = await searchParams + const quotationId = parseInt(id) + + if (isNaN(quotationId)) { + notFound() + } + + // 견적서 데이터 조회 + const { data: quotation, error } = await getTechSalesVendorQuotation(quotationId) + + if (error || !quotation) { + notFound() + } + + const rfq = quotation.rfq + const vendor = quotation.vendor + + // 상태별 배지 색상 + const getStatusBadgeVariant = (status: string) => { + switch (status) { + case "Draft": + return "secondary" + case "Submitted": + return "default" + case "Revised": + return "outline" + case "Rejected": + return "destructive" + case "Accepted": + return "success" + default: + return "secondary" + } + } + + const getStatusLabel = (status: string) => { + switch (status) { + case "Draft": + return "초안" + case "Submitted": + return "제출됨" + case "Revised": + return "수정됨" + case "Rejected": + return "반려됨" + case "Accepted": + return "승인됨" + default: + return status + } + } + + return ( + <Shell variant="sidebar" className="gap-4"> + {/* 헤더 */} + <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between"> + <div className="space-y-1"> + <div className="flex items-center gap-2"> + <h1 className="text-2xl font-bold">견적서 상세</h1> + <Badge variant={getStatusBadgeVariant(quotation.status)}> + {getStatusLabel(quotation.status)} + </Badge> + </div> + <div className="flex items-center gap-4 text-sm text-muted-foreground"> + <span>RFQ: {rfq?.rfqCode || "미할당"}</span> + <span>•</span> + <span>벤더: {vendor?.vendorName}</span> + <span>•</span> + <span>마감일: {rfq?.dueDate ? formatDate(rfq.dueDate) : "N/A"}</span> + </div> + </div> + + {/* 우측 상단 버튼 */} + <div className="flex items-center justify-end"> + <BackButton segmentsToRemove={1}> + 목록으로 돌아가기 + </BackButton> + </div> + </div> + + <Separator /> + + {/* 탭 컨텐츠 */} + <div className="flex-1 min-h-0"> + <TechSalesQuotationTabs + quotation={quotation} + defaultTab={tab || "project"} + /> + </div> + </Shell> + ) +}
\ 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 new file mode 100644 index 00000000..5b0ffb61 --- /dev/null +++ b/app/[lng]/partners/(partners)/techsales/rfq-ship/page.tsx @@ -0,0 +1,219 @@ +// app/vendor/quotations/page.tsx +import * as React from "react"; +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 { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; +import { Shell } from "@/components/shell"; +import { getValidFilters } from "@/lib/data-table"; +import { type SearchParams } from "@/types/table"; +import { searchParamsVendorRfqCache } from "@/lib/techsales-rfq/validations"; +import { + TECH_SALES_QUOTATION_STATUSES, + TECH_SALES_QUOTATION_STATUS_CONFIG +} from "@/db/schema"; + +import { getQuotationStatusCounts, getVendorQuotations } from "@/lib/techsales-rfq/service"; +import { VendorQuotationsTable } from "@/lib/techsales-rfq/vendor-response/table/vendor-quotations-table"; + +export const metadata: Metadata = { + title: "기술영업 견적서 관리", + description: "기술영업 RFQ 견적서를 관리합니다.", +}; + +interface VendorQuotationsPageProps { + searchParams: SearchParams; +} + +export default async function VendorQuotationsPage({ + searchParams, +}: VendorQuotationsPageProps) { + // 세션 확인 + const session = await getServerSession(authOptions); + + if (!session?.user) { + return ( + <Shell> + <div className="flex min-h-[400px] flex-col items-center justify-center space-y-4"> + <div className="text-center"> + <h2 className="text-2xl font-bold tracking-tight">로그인이 필요합니다</h2> + <p className="text-muted-foreground"> + 견적서를 확인하려면 로그인해주세요. + </p> + </div> + <Button asChild> + <Link href="/api/auth/signin"> + <LogIn className="mr-2 h-4 w-4" /> + 로그인 + </Link> + </Button> + </div> + </Shell> + ); + } + + // 벤더 ID 확인 (사용자의 회사 ID가 벤더 ID) + const vendorId = session.user.companyId; + if (!vendorId) { + return ( + <Shell> + <div className="flex min-h-[400px] flex-col items-center justify-center space-y-4"> + <div className="text-center"> + <h2 className="text-2xl font-bold tracking-tight">회사 정보가 없습니다</h2> + <p className="text-muted-foreground"> + 견적서를 확인하려면 회사 정보가 필요합니다. + </p> + </div> + </div> + </Shell> + ); + } + + // 검색 파라미터 파싱 및 검증 + const search = searchParamsVendorRfqCache.parse(searchParams); + const validFilters = getValidFilters(search.filters); + + // 견적서 상태별 개수 조회 + const statusCountsPromise = getQuotationStatusCounts(vendorId.toString()); + + // 견적서 목록 조회 + const quotationsPromise = getVendorQuotations( + { + flags: search.flags, + page: search.page, + perPage: search.perPage, + sort: search.sort, + filters: validFilters, + joinOperator: search.joinOperator, + search: search.search, + from: search.from, + to: search.to, + }, + vendorId.toString() + ); + + 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> + <p className="text-muted-foreground"> + 할당받은 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"> + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={12} + searchableColumnCount={2} + filterableColumnCount={3} + cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem"]} + shrinkZero + /> + } + > + <div className="h-full overflow-auto"> + <VendorQuotationsTable promises={Promise.all([quotationsPromise.then(result => ({ data: result.data, pageCount: result.pageCount }))])} /> + </div> + </React.Suspense> + </div> + </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/not-found.tsx b/app/not-found.tsx new file mode 100644 index 00000000..b966d978 --- /dev/null +++ b/app/not-found.tsx @@ -0,0 +1,285 @@ +'use client' + +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" +import { Separator } from "@/components/ui/separator" +import { ArrowLeft, Building2, Users, Info, AlertCircle, Copy } from "lucide-react" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { useState, useEffect } from "react" +import { toast } from "sonner" + +// 에러 정보 타입 +interface ErrorInfo { + timestamp: string; + url: string; + userAgent: string; + platform: string; + language: string; + cookieEnabled: boolean; + onLine: boolean; + screen: { + width: number; + height: number; + colorDepth: number; + }; + viewport: { + width: number; + height: number; + }; + referrer: string; + localStorage: boolean; + sessionStorage: boolean; +} + +export default function NotFound() { + const router = useRouter() + const [errorInfo, setErrorInfo] = useState<ErrorInfo | null>(null) + + useEffect(() => { + // 브라우저 환경 정보 수집 + const collectErrorInfo = () => { + const info: ErrorInfo = { + timestamp: new Date().toISOString(), + url: window.location.href, + userAgent: navigator.userAgent, + platform: navigator.platform, + language: navigator.language, + cookieEnabled: navigator.cookieEnabled, + onLine: navigator.onLine, + screen: { + width: screen.width, + height: screen.height, + colorDepth: screen.colorDepth, + }, + viewport: { + width: window.innerWidth, + height: window.innerHeight, + }, + referrer: document.referrer || '직접 접근', + localStorage: typeof Storage !== 'undefined', + sessionStorage: typeof Storage !== 'undefined', + } + setErrorInfo(info) + } + + collectErrorInfo() + }, []) + + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text).then(() => { + toast.success("클립보드에 복사되었습니다") + }).catch(() => { + toast.error("복사에 실패했습니다") + }) + } + + const copyErrorInfo = () => { + if (!errorInfo) return + + const errorText = ` +=== 404 에러 정보 === +시간: ${errorInfo.timestamp} +URL: ${errorInfo.url} +리퍼러: ${errorInfo.referrer} +브라우저: ${errorInfo.userAgent} +플랫폼: ${errorInfo.platform} +언어: ${errorInfo.language} +화면 해상도: ${errorInfo.screen.width}x${errorInfo.screen.height} +뷰포트: ${errorInfo.viewport.width}x${errorInfo.viewport.height} +온라인 상태: ${errorInfo.onLine ? '온라인' : '오프라인'} +쿠키 활성화: ${errorInfo.cookieEnabled ? '예' : '아니오'} + `.trim() + + copyToClipboard(errorText) + } + + return ( + <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4"> + <Card className="w-full max-w-2xl shadow-lg"> + <CardHeader className="text-center space-y-4"> + <div className="mx-auto w-24 h-24 bg-slate-100 dark:bg-slate-800 rounded-full flex items-center justify-center"> + <span className="text-4xl font-bold text-slate-600 dark:text-slate-400">404</span> + </div> + <CardTitle className="text-3xl font-bold text-slate-900 dark:text-slate-100"> + 이 페이지는 개발중이거나, 잘못된 URL 입니다. + </CardTitle> + <CardDescription className="text-lg text-slate-600 dark:text-slate-400"> + THIS PAGE IS UNDER DEVELOPMENT OR INVALID URL. + </CardDescription> + </CardHeader> + + <CardContent className="space-y-6"> + <Separator /> + + <div className="text-center text-sm text-slate-500 dark:text-slate-400"> + 아래 버튼을 통해 원하는 페이지로 이동하세요 + </div> + + <div className="grid gap-4 sm:grid-cols-3"> + {/* SHI 사용자 홈 */} + <Button + asChild + variant="outline" + className="h-auto p-4 flex flex-col items-center space-y-2 hover:bg-blue-50 hover:border-blue-200 dark:hover:bg-blue-950" + > + <Link href="/ko/evcp/dashboard"> + <Building2 className="h-6 w-6 text-blue-600 dark:text-blue-400" /> + <span className="font-medium">SHI 사용자 홈</span> + <span className="text-xs text-slate-500 dark:text-slate-400">EVCP 대시보드</span> + </Link> + </Button> + + {/* 벤더 홈 */} + <Button + asChild + variant="outline" + className="h-auto p-4 flex flex-col items-center space-y-2 hover:bg-green-50 hover:border-green-200 dark:hover:bg-green-950" + > + <Link href="/ko/partners"> + <Users className="h-6 w-6 text-green-600 dark:text-green-400" /> + <span className="font-medium">벤더 홈</span> + <span className="text-xs text-slate-500 dark:text-slate-400">협력업체 포털</span> + </Link> + </Button> + + {/* 뒤로 가기 */} + <Button + onClick={() => router.back()} + variant="outline" + className="h-auto p-4 flex flex-col items-center space-y-2 hover:bg-slate-50 hover:border-slate-200 dark:hover:bg-slate-800" + > + <ArrowLeft className="h-6 w-6 text-slate-600 dark:text-slate-400" /> + <span className="font-medium">뒤로 가기</span> + <span className="text-xs text-slate-500 dark:text-slate-400">이전 페이지로</span> + </Button> + </div> + + <Separator /> + + <div className="flex justify-center gap-4"> + + + {/* 에러 정보 다이얼로그 */} + <Dialog> + <DialogTrigger asChild> + <Button variant="ghost" size="sm"> + <Info className="h-4 w-4 mr-2" /> + 에러 정보 + </Button> + </DialogTrigger> + <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <AlertCircle className="h-5 w-5" /> + 에러 상세 정보 + </DialogTitle> + <DialogDescription> + 기술 지원을 위한 상세 환경 정보입니다. + </DialogDescription> + </DialogHeader> + + {errorInfo && ( + <div className="space-y-4"> + <div className="flex justify-end"> + <Button onClick={copyErrorInfo} size="sm" variant="outline"> + <Copy className="h-4 w-4 mr-2" /> + 전체 복사 + </Button> + </div> + + <div className="space-y-3"> + <div className="grid gap-2"> + <label className="text-sm font-medium">발생 시간</label> + <div className="p-2 bg-muted rounded text-sm font-mono"> + {errorInfo.timestamp} + </div> + </div> + + <div className="grid gap-2"> + <label className="text-sm font-medium">요청 URL</label> + <div className="p-2 bg-muted rounded text-sm font-mono break-all"> + {errorInfo.url} + </div> + </div> + + <div className="grid gap-2"> + <label className="text-sm font-medium">이전 페이지</label> + <div className="p-2 bg-muted rounded text-sm font-mono break-all"> + {errorInfo.referrer} + </div> + </div> + + <div className="grid gap-2"> + <label className="text-sm font-medium">브라우저 정보</label> + <div className="p-2 bg-muted rounded text-sm font-mono break-all"> + {errorInfo.userAgent} + </div> + </div> + + <div className="grid grid-cols-2 gap-4"> + <div className="grid gap-2"> + <label className="text-sm font-medium">플랫폼</label> + <div className="p-2 bg-muted rounded text-sm font-mono"> + {errorInfo.platform} + </div> + </div> + + <div className="grid gap-2"> + <label className="text-sm font-medium">언어</label> + <div className="p-2 bg-muted rounded text-sm font-mono"> + {errorInfo.language} + </div> + </div> + </div> + + <div className="grid grid-cols-2 gap-4"> + <div className="grid gap-2"> + <label className="text-sm font-medium">화면 해상도</label> + <div className="p-2 bg-muted rounded text-sm font-mono"> + {errorInfo.screen.width} × {errorInfo.screen.height} + </div> + </div> + + <div className="grid gap-2"> + <label className="text-sm font-medium">뷰포트 크기</label> + <div className="p-2 bg-muted rounded text-sm font-mono"> + {errorInfo.viewport.width} × {errorInfo.viewport.height} + </div> + </div> + </div> + + <div className="grid grid-cols-3 gap-4"> + <div className="grid gap-2"> + <label className="text-sm font-medium">온라인 상태</label> + <div className="p-2 bg-muted rounded text-sm font-mono"> + {errorInfo.onLine ? '온라인' : '오프라인'} + </div> + </div> + + <div className="grid gap-2"> + <label className="text-sm font-medium">쿠키 활성화</label> + <div className="p-2 bg-muted rounded text-sm font-mono"> + {errorInfo.cookieEnabled ? '예' : '아니오'} + </div> + </div> + + <div className="grid gap-2"> + <label className="text-sm font-medium">색상 깊이</label> + <div className="p-2 bg-muted rounded text-sm font-mono"> + {errorInfo.screen.colorDepth}bit + </div> + </div> + </div> + </div> + </div> + )} + </DialogContent> + </Dialog> + </div> + </CardContent> + </Card> + </div> + ) +}
\ No newline at end of file |
