Add project management support and integrate customer-project functionality

This commit is contained in:
2025-07-15 18:23:53 +00:00
parent 2707aa48bc
commit 03f633ae52
26 changed files with 1135 additions and 43 deletions

View File

@@ -0,0 +1,37 @@
import {NextRequest, NextResponse} from "next/server";
import {serverCall} from "@/lib/api/serverCall";
import {projectRoutes} from "@/app/api/projects/projectRoutes";
export async function GET(request: NextRequest) {
try {
// Extract project ID from the URL
const segments = request.url.split("/");
const projectId = segments.pop();
if (!projectId) {
return NextResponse.json(
{error: "Project ID is required"},
{status: 400}
);
}
// Perform server call to fetch the project details
const response = await serverCall(projectRoutes.getById(projectId), "GET");
if (!response.ok) {
return NextResponse.json(
{error: "Project not found"},
{status: response.status}
);
}
const project = await response.json();
return NextResponse.json(project);
} catch (error) {
console.error("Error fetching project:", error);
return NextResponse.json(
{error: "Failed to fetch project"},
{status: 500}
);
}
}

View File

@@ -0,0 +1,16 @@
import {NextRequest, NextResponse} from "next/server";
import {serverCall} from "@/lib/api/serverCall";
import {projectRoutes} from "@/app/api/projects/projectRoutes";
export async function GET(request: NextRequest) {
const segments = request.url.split('/');
const id = segments[segments.indexOf('customer') + 1];
const response = await serverCall(projectRoutes.getProjectByCustomerId(id), "GET");
if (!response.ok) {
return NextResponse.json({error: "Customer not found"}, {status: 404});
}
const customer = await response.json();
return NextResponse.json(customer);
}

View File

@@ -0,0 +1,6 @@
export const projectRoutes = {
'create': '/api/projects',
getById: (id: string) => `/api/projects/${id}`,
getProjectByCustomerId: (customerId: string) => `/api/projects/customer/${customerId}`
}
;

View File

@@ -0,0 +1,21 @@
import {NextRequest, NextResponse} from "next/server";
import {serverCall} from "@/lib/api/serverCall";
import {projectRoutes} from "@/app/api/projects/projectRoutes";
export async function POST(req: NextRequest) {
try {
// Parse the incoming JSON payload
const body = await req.json();
// Make a POST request to the backend using serverCall
const response = await serverCall(projectRoutes.create, "POST", body);
// Parse and return the backend response
const result = await response.json();
return NextResponse.json(result);
} catch (error) {
// Handle errors gracefully
console.error("Error creating project:", error);
return NextResponse.json({error: "Failed to create project"}, {status: 500});
}
}

View File

@@ -1,7 +0,0 @@
export default function Home() {
return (
<div>
apps
</div>
);
}

View File

@@ -10,6 +10,7 @@ import CustomerDetailContent from "@/components/customers/details/CustomerDetail
import CustomerInformationContent from "@/components/customers/details/sub/ContactInformationContent";
import CustomerPhoneNumberContent from "@/components/customers/details/sub/CustomerPhoneNumberContent";
import CustomerNotesContent from "@/components/customers/details/sub/CustomerNotesContent";
import CustomerProjectsContent from "@/components/customers/details/sub/CustomerProjectsContent";
import {Customer} from "@/services/customers/entities/customer";
export default function CustomerDetailPage() {
@@ -51,7 +52,7 @@ export default function CustomerDetailPage() {
})
.catch((error) => {
if (!isMounted) return;
console.error('Error fetching customer:', error);
console.error("Error fetching customer:", error);
setCustomer(null);
setError("Fehler beim Laden der Kundendaten");
})
@@ -73,17 +74,17 @@ export default function CustomerDetailPage() {
const formatDate = (date: string) => {
try {
const formattedDate = new Date(date).toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
const formattedDate = new Date(date).toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
return formattedDate === 'Invalid Date' ? '-' : formattedDate;
return formattedDate === "Invalid Date" ? "-" : formattedDate;
} catch (error) {
console.error('Error formatting date:', error);
return '-';
console.error("Error formatting date:", error);
return "-";
}
};
@@ -93,7 +94,7 @@ export default function CustomerDetailPage() {
: "Erstellt von - am -",
lastActivityInfo: customer
? `Letzte Aktivität: ${customer.updatedBy || "-"} am ${formatDate(customer.updatedAt)}`
: "Letzte Aktivität: - am -"
: "Letzte Aktivität: - am -",
};
const renderMetadata = () => {
@@ -140,26 +141,29 @@ export default function CustomerDetailPage() {
<CustomerDetailContent
loading={loading}
customer={customer}
sections={
customer
? [
<CustomerInformationContent
key="customerInformation"
customer={customer}
handleOpenDialog={handleOpenDialog}
/>,
<CustomerPhoneNumberContent
key="customerPhoneNumberInfo"
customer={customer}
handleOpenDialog={handleOpenDialog}
/>,
<CustomerNotesContent
key="customerNotes"
customer={customer}
handleOpenDialog={handleOpenDialog}
/>
]
: []
informationSection={
<CustomerInformationContent
customer={customer!}
handleOpenDialog={handleOpenDialog}
/>
}
phoneNumberSection={
<CustomerPhoneNumberContent
customer={customer!}
handleOpenDialog={handleOpenDialog}
/>
}
notesSection={
<CustomerNotesContent
customer={customer!}
handleOpenDialog={handleOpenDialog}
/>
}
projectsSection={
<CustomerProjectsContent
customer={customer!}
handleOpenDialog={handleOpenDialog}
/>
}
/>
)}
@@ -169,4 +173,4 @@ export default function CustomerDetailPage() {
</Dialog>
</div>
);
}
}

View File

@@ -0,0 +1,102 @@
"use client";
import React, {useEffect, useState} from "react";
import {useParams, useRouter} from "next/navigation";
import {ChevronLeft} from "lucide-react";
import {Button} from "@/components/ui/button";
import {Skeleton} from "@/components/ui/skeleton";
import {CustomerProject} from "@/services/projects/entities/customer-project";
export default function ProjectDetailPage() {
const router = useRouter();
const {id} = useParams<{ id: string }>();
const [project, setProject] = useState<CustomerProject | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) {
setError("No project ID provided");
setLoading(false);
return;
}
let isMounted = true;
setLoading(true);
setError(null);
fetch(`/api/projects/${id}`)
.then(async (response) => {
if (!isMounted) return;
if (response.status === 404) {
setError("Project not found");
setProject(null);
return;
}
if (!response.ok) {
throw new Error("Failed to fetch project data");
}
const result = await response.json();
setProject(result);
})
.catch((err) => {
if (!isMounted) return;
console.error("Error fetching project:", err);
setProject(null);
setError("Error loading project data");
})
.finally(() => {
if (isMounted) {
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, [id]);
if (loading) {
return (
<div className="p-6">
<Skeleton className="w-full h-8 mb-4"/>
<Skeleton className="w-full h-6 mb-2"/>
<Skeleton className="w-3/4 h-6"/>
</div>
);
}
if (error) {
return (
<div className="p-6 text-red-500">
<p>{error}</p>
<Button variant="ghost" onClick={() => router.back()}>
<ChevronLeft className="w-4 h-4 mr-1"/>
Go back
</Button>
</div>
);
}
return (
<div className="h-full w-full p-6 space-y-4 text-sm">
<div className="flex justify-between items-start">
<Button
variant="ghost"
onClick={() => router.back()}
aria-label="Go back"
>
<ChevronLeft className="w-4 h-4 mr-1"/>
Zurück
</Button>
</div>
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">{project?.name}</h1>
<p className="text-lg">{project?.description}</p>
</div>
</div>
);
}

View File

@@ -0,0 +1,7 @@
export default function ProjectsPage() {
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold">Project Overview</h1>
</div>
);
}