260322:1648 Correct Coresspondence / Doing RFA / Correct CI
This commit is contained in:
@@ -1,25 +1,25 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { use, useState } from "react";
|
||||
import { notFound, useRouter, useSearchParams } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ArrowLeft, Download, FileText, Loader2, Pencil, Upload, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { RevisionHistory } from "@/components/drawings/revision-history";
|
||||
import { format } from "date-fns";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { contractDrawingService } from "@/lib/services/contract-drawing.service";
|
||||
import { shopDrawingService } from "@/lib/services/shop-drawing.service";
|
||||
import { asBuiltDrawingService } from "@/lib/services/asbuilt-drawing.service";
|
||||
import { useUpdateContractDrawing, useUploadRevision } from "@/hooks/use-drawing";
|
||||
import { use, useState } from 'react';
|
||||
import { notFound, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { ArrowLeft, Download, FileText, Loader2, Pencil, Upload, X } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { RevisionHistory } from '@/components/drawings/revision-history';
|
||||
import { format } from 'date-fns';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { contractDrawingService } from '@/lib/services/contract-drawing.service';
|
||||
import { shopDrawingService } from '@/lib/services/shop-drawing.service';
|
||||
import { asBuiltDrawingService } from '@/lib/services/asbuilt-drawing.service';
|
||||
import { useUpdateContractDrawing, useUploadRevision } from '@/hooks/use-drawing';
|
||||
|
||||
type DrawingType = "CONTRACT" | "SHOP" | "AS_BUILT";
|
||||
type DrawingType = 'CONTRACT' | 'SHOP' | 'AS_BUILT';
|
||||
|
||||
interface FetchedDrawing {
|
||||
_type: DrawingType;
|
||||
@@ -31,41 +31,56 @@ interface FetchedDrawing {
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
currentRevision?: { title?: string; revisionNumber?: string; legacyDrawingNumber?: string };
|
||||
revisions?: { revisionId?: number; uuid: string; revisionNumber: string; title?: string; legacyDrawingNumber?: string; revisionDate: string; revisionDescription?: string; revisedByName: string; fileUrl: string; isCurrent: boolean | null; createdBy?: number; updatedBy?: number }[];
|
||||
revisions?: {
|
||||
revisionId?: number;
|
||||
uuid: string;
|
||||
revisionNumber: string;
|
||||
title?: string;
|
||||
legacyDrawingNumber?: string;
|
||||
revisionDate: string;
|
||||
revisionDescription?: string;
|
||||
revisedByName: string;
|
||||
fileUrl: string;
|
||||
isCurrent: boolean | null;
|
||||
createdBy?: number;
|
||||
updatedBy?: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
async function fetchDrawingByUuid(uuid: string): Promise<FetchedDrawing | null> {
|
||||
try {
|
||||
const result = await contractDrawingService.getByUuid(uuid);
|
||||
if (result?.data) return { ...result.data, _type: "CONTRACT" as const };
|
||||
} catch { /* not found */ }
|
||||
if (result?.data) return { ...result.data, _type: 'CONTRACT' as const };
|
||||
} catch {
|
||||
/* not found */
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await shopDrawingService.getByUuid(uuid);
|
||||
if (result?.data) return { ...result.data, _type: "SHOP" as const };
|
||||
} catch { /* not found */ }
|
||||
if (result?.data) return { ...result.data, _type: 'SHOP' as const };
|
||||
} catch {
|
||||
/* not found */
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await asBuiltDrawingService.getByUuid(uuid);
|
||||
if (result?.data) return { ...result.data, _type: "AS_BUILT" as const };
|
||||
} catch { /* not found */ }
|
||||
if (result?.data) return { ...result.data, _type: 'AS_BUILT' as const };
|
||||
} catch {
|
||||
/* not found */
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function DrawingDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ uuid: string }>;
|
||||
}) {
|
||||
export default function DrawingDetailPage({ params }: { params: Promise<{ uuid: string }> }) {
|
||||
const { uuid } = use(params);
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const isEditMode = searchParams.get("edit") === "true";
|
||||
const isUploadMode = searchParams.get("upload") === "true";
|
||||
const isEditMode = searchParams.get('edit') === 'true';
|
||||
const isUploadMode = searchParams.get('upload') === 'true';
|
||||
|
||||
const { data: drawing, isLoading } = useQuery({
|
||||
queryKey: ["drawing-detail", uuid],
|
||||
queryKey: ['drawing-detail', uuid],
|
||||
queryFn: () => fetchDrawingByUuid(uuid),
|
||||
enabled: !!uuid,
|
||||
});
|
||||
@@ -100,8 +115,8 @@ export default function DrawingDetailPage({
|
||||
);
|
||||
}
|
||||
|
||||
const drawingNumber = drawing.contractDrawingNo || drawing.drawingNumber || "N/A";
|
||||
const title = drawing.title || drawing.currentRevision?.title || "Untitled";
|
||||
const drawingNumber = drawing.contractDrawingNo || drawing.drawingNumber || 'N/A';
|
||||
const title = drawing.title || drawing.currentRevision?.title || 'Untitled';
|
||||
const revisions = drawing.revisions || [];
|
||||
|
||||
return (
|
||||
@@ -129,7 +144,7 @@ export default function DrawingDetailPage({
|
||||
Edit Detail
|
||||
</Link>
|
||||
</Button>
|
||||
{drawing._type !== "CONTRACT" && (
|
||||
{drawing._type !== 'CONTRACT' && (
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={`/drawings/${uuid}?upload=true`}>
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
@@ -155,12 +170,10 @@ export default function DrawingDetailPage({
|
||||
</div>
|
||||
|
||||
{/* Edit Detail Form */}
|
||||
{isEditMode && (
|
||||
<EditDetailForm drawing={drawing} uuid={uuid} onDone={() => router.push(`/drawings/${uuid}`)} />
|
||||
)}
|
||||
{isEditMode && <EditDetailForm drawing={drawing} uuid={uuid} onDone={() => router.push(`/drawings/${uuid}`)} />}
|
||||
|
||||
{/* Upload Revision Form */}
|
||||
{isUploadMode && drawing._type !== "CONTRACT" && (
|
||||
{isUploadMode && drawing._type !== 'CONTRACT' && (
|
||||
<UploadRevisionForm drawingType={drawing._type} uuid={uuid} onDone={() => router.push(`/drawings/${uuid}`)} />
|
||||
)}
|
||||
|
||||
@@ -194,7 +207,7 @@ export default function DrawingDetailPage({
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Created</p>
|
||||
<p className="font-medium mt-1">
|
||||
{drawing.createdAt ? format(new Date(drawing.createdAt), "dd MMM yyyy") : "N/A"}
|
||||
{drawing.createdAt ? format(new Date(drawing.createdAt), 'dd MMM yyyy') : 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,36 +237,28 @@ export default function DrawingDetailPage({
|
||||
}
|
||||
|
||||
/* ─── Edit Detail Form ─── */
|
||||
function EditDetailForm({
|
||||
drawing,
|
||||
uuid,
|
||||
onDone,
|
||||
}: {
|
||||
drawing: FetchedDrawing;
|
||||
uuid: string;
|
||||
onDone: () => void;
|
||||
}) {
|
||||
function EditDetailForm({ drawing, uuid, onDone }: { drawing: FetchedDrawing; uuid: string; onDone: () => void }) {
|
||||
const updateMutation = useUpdateContractDrawing();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [formTitle, setFormTitle] = useState(drawing.title || drawing.currentRevision?.title || "");
|
||||
const [formDrawingNo, setFormDrawingNo] = useState(drawing.contractDrawingNo || drawing.drawingNumber || "");
|
||||
const [formVolumePage, setFormVolumePage] = useState(drawing.volumePage?.toString() || "");
|
||||
const [formTitle, setFormTitle] = useState(drawing.title || drawing.currentRevision?.title || '');
|
||||
const [formDrawingNo, setFormDrawingNo] = useState(drawing.contractDrawingNo || drawing.drawingNumber || '');
|
||||
const [formVolumePage, setFormVolumePage] = useState(drawing.volumePage?.toString() || '');
|
||||
|
||||
const handleSave = () => {
|
||||
if (drawing._type === "CONTRACT") {
|
||||
if (drawing._type === 'CONTRACT') {
|
||||
updateMutation.mutate(
|
||||
{
|
||||
uuid,
|
||||
data: {
|
||||
title: formTitle,
|
||||
contractDrawingNo: formDrawingNo,
|
||||
volumePage: formVolumePage ? parseInt(formVolumePage, 10) : undefined,
|
||||
volumePage: formVolumePage ? Number(formVolumePage) : undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["drawing-detail", uuid] });
|
||||
queryClient.invalidateQueries({ queryKey: ['drawing-detail', uuid] });
|
||||
onDone();
|
||||
},
|
||||
}
|
||||
@@ -273,7 +278,7 @@ function EditDetailForm({
|
||||
<Label>Title</Label>
|
||||
<Input value={formTitle} onChange={(e) => setFormTitle(e.target.value)} />
|
||||
</div>
|
||||
{drawing._type === "CONTRACT" && (
|
||||
{drawing._type === 'CONTRACT' && (
|
||||
<div>
|
||||
<Label>Volume Page</Label>
|
||||
<Input type="number" value={formVolumePage} onChange={(e) => setFormVolumePage(e.target.value)} />
|
||||
@@ -306,10 +311,10 @@ function UploadRevisionForm({
|
||||
const uploadMutation = useUploadRevision(drawingType);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [revisionLabel, setRevisionLabel] = useState("");
|
||||
const [revTitle, setRevTitle] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [legacyNo, setLegacyNo] = useState("");
|
||||
const [revisionLabel, setRevisionLabel] = useState('');
|
||||
const [revTitle, setRevTitle] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [legacyNo, setLegacyNo] = useState('');
|
||||
|
||||
const handleUpload = () => {
|
||||
if (!revisionLabel || !revTitle) return;
|
||||
@@ -327,7 +332,7 @@ function UploadRevisionForm({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["drawing-detail", uuid] });
|
||||
queryClient.invalidateQueries({ queryKey: ['drawing-detail', uuid] });
|
||||
onDone();
|
||||
},
|
||||
}
|
||||
@@ -341,7 +346,11 @@ function UploadRevisionForm({
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>Revision Label *</Label>
|
||||
<Input placeholder="e.g. A, B, 1, 2" value={revisionLabel} onChange={(e) => setRevisionLabel(e.target.value)} />
|
||||
<Input
|
||||
placeholder="e.g. A, B, 1, 2"
|
||||
value={revisionLabel}
|
||||
onChange={(e) => setRevisionLabel(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Legacy Drawing No.</Label>
|
||||
@@ -354,7 +363,12 @@ function UploadRevisionForm({
|
||||
</div>
|
||||
<div>
|
||||
<Label>Description</Label>
|
||||
<Textarea placeholder="What changed in this revision?" value={description} onChange={(e) => setDescription(e.target.value)} rows={3} />
|
||||
<Textarea
|
||||
placeholder="What changed in this revision?"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-3 pt-2">
|
||||
<Button onClick={handleUpload} disabled={uploadMutation.isPending || !revisionLabel || !revTitle}>
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { DrawingList } from "@/components/drawings/list";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Upload, Loader2 } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useProjects } from "@/hooks/use-master-data";
|
||||
import { useState } from 'react';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { DrawingList } from '@/components/drawings/list';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Upload, Loader2 } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useProjects } from '@/hooks/use-master-data';
|
||||
|
||||
export default function DrawingsPage() {
|
||||
const [selectedProjectUuid, setSelectedProjectUuid] = useState<string | undefined>(undefined);
|
||||
@@ -24,9 +18,7 @@ export default function DrawingsPage() {
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Drawings</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Manage contract, shop, and as-built drawings
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1">Manage contract, shop, and as-built drawings</p>
|
||||
</div>
|
||||
<Link href="/drawings/upload">
|
||||
<Button>
|
||||
@@ -39,10 +31,7 @@ export default function DrawingsPage() {
|
||||
{/* Project Selector */}
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm font-medium">Project:</span>
|
||||
<Select
|
||||
value={selectedProjectUuid ?? ""}
|
||||
onValueChange={(v) => setSelectedProjectUuid(v || undefined)}
|
||||
>
|
||||
<Select value={selectedProjectUuid ?? ''} onValueChange={(v) => setSelectedProjectUuid(v || undefined)}>
|
||||
<SelectTrigger className="w-[300px]">
|
||||
{isLoadingProjects ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -72,43 +61,42 @@ export default function DrawingsPage() {
|
||||
}
|
||||
|
||||
function DrawingTabs({ projectUuid }: { projectUuid: string }) {
|
||||
const [search, setSearch] = useState("");
|
||||
const [search, setSearch] = useState('');
|
||||
// We can add more specific filters here (e.g. category) later
|
||||
|
||||
return (
|
||||
<Tabs defaultValue="contract" className="w-full">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<TabsList className="grid w-full grid-cols-3 max-w-[400px]">
|
||||
<TabsTrigger value="contract">Contract</TabsTrigger>
|
||||
<TabsTrigger value="shop">Shop</TabsTrigger>
|
||||
<TabsTrigger value="asbuilt">As Built</TabsTrigger>
|
||||
</TabsList>
|
||||
<Tabs defaultValue="contract" className="w-full">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<TabsList className="grid w-full grid-cols-3 max-w-[400px]">
|
||||
<TabsTrigger value="contract">Contract</TabsTrigger>
|
||||
<TabsTrigger value="shop">Shop</TabsTrigger>
|
||||
<TabsTrigger value="asbuilt">As Built</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search drawings..."
|
||||
className="h-10 w-[250px] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search drawings..."
|
||||
className="h-10 w-[250px] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabsContent value="contract" className="mt-0">
|
||||
<DrawingList type="CONTRACT" projectUuid={projectUuid} filters={{ search }} />
|
||||
</TabsContent>
|
||||
<TabsContent value="contract" className="mt-0">
|
||||
<DrawingList type="CONTRACT" projectUuid={projectUuid} filters={{ search }} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="shop" className="mt-0">
|
||||
<DrawingList type="SHOP" projectUuid={projectUuid} filters={{ search }} />
|
||||
</TabsContent>
|
||||
<TabsContent value="shop" className="mt-0">
|
||||
<DrawingList type="SHOP" projectUuid={projectUuid} filters={{ search }} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="asbuilt" className="mt-0">
|
||||
<DrawingList type="AS_BUILT" projectUuid={projectUuid} filters={{ search }} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
<TabsContent value="asbuilt" className="mt-0">
|
||||
<DrawingList type="AS_BUILT" projectUuid={projectUuid} filters={{ search }} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DrawingUploadForm } from "@/components/drawings/upload-form";
|
||||
import { DrawingUploadForm } from '@/components/drawings/upload-form';
|
||||
|
||||
// Force dynamic rendering to prevent build-time prerendering issues
|
||||
export const dynamic = 'force-dynamic';
|
||||
@@ -11,9 +11,7 @@ export default function DrawingUploadPage() {
|
||||
<div className="max-w-4xl mx-auto py-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold">Upload Drawing</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Upload a new contract or shop drawing revision.
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1">Upload a new contract or shop drawing revision.</p>
|
||||
</div>
|
||||
|
||||
<DrawingUploadForm />
|
||||
|
||||
Reference in New Issue
Block a user