298 lines
9.1 KiB
JavaScript
298 lines
9.1 KiB
JavaScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { Card, CardHeader, CardContent } from "@/components/ui/Card";
|
|
import Button from "@/components/ui/Button";
|
|
import Badge from "@/components/ui/Badge";
|
|
import { Input } from "@/components/ui/Input";
|
|
import PageContainer from "@/components/ui/PageContainer";
|
|
import PageHeader from "@/components/ui/PageHeader";
|
|
import SearchBar from "@/components/ui/SearchBar";
|
|
import { LoadingState } from "@/components/ui/States";
|
|
import { formatDate } from "@/lib/utils";
|
|
|
|
export default function ProjectListPage() {
|
|
const [projects, setProjects] = useState([]);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
const [filteredProjects, setFilteredProjects] = useState([]);
|
|
useEffect(() => {
|
|
fetch("/api/projects")
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
setProjects(data);
|
|
setFilteredProjects(data);
|
|
});
|
|
}, []);
|
|
|
|
// Filter projects based on search term
|
|
useEffect(() => {
|
|
if (!searchTerm.trim()) {
|
|
setFilteredProjects(projects);
|
|
} else {
|
|
const filtered = projects.filter((project) => {
|
|
const searchLower = searchTerm.toLowerCase();
|
|
return (
|
|
project.project_name?.toLowerCase().includes(searchLower) ||
|
|
project.wp?.toLowerCase().includes(searchLower) ||
|
|
project.plot?.toLowerCase().includes(searchLower) ||
|
|
project.investment_number?.toLowerCase().includes(searchLower) ||
|
|
project.address?.toLowerCase().includes(searchLower)
|
|
);
|
|
});
|
|
setFilteredProjects(filtered);
|
|
}
|
|
}, [searchTerm, projects]);
|
|
|
|
async function handleDelete(id) {
|
|
const confirmed = confirm("Are you sure you want to delete this project?");
|
|
if (!confirmed) return;
|
|
|
|
const res = await fetch(`/api/projects/${id}`, {
|
|
method: "DELETE",
|
|
});
|
|
if (res.ok) {
|
|
setProjects((prev) => prev.filter((p) => p.project_id !== id));
|
|
}
|
|
}
|
|
|
|
const handleSearchChange = (e) => {
|
|
setSearchTerm(e.target.value);
|
|
};
|
|
return (
|
|
<PageContainer>
|
|
<PageHeader title="Projects" description="Manage and track your projects">
|
|
<div className="flex gap-2">
|
|
<Link href="/projects/map">
|
|
<Button variant="outline" size="lg">
|
|
<svg
|
|
className="w-5 h-5 mr-2"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
|
|
/>
|
|
</svg>
|
|
Map View
|
|
</Button>
|
|
</Link>
|
|
<Link href="/projects/new">
|
|
<Button variant="primary" size="lg">
|
|
<svg
|
|
className="w-5 h-5 mr-2"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M12 4v16m8-8H4"
|
|
/>
|
|
</svg>
|
|
Add Project
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</PageHeader>
|
|
|
|
<SearchBar
|
|
searchTerm={searchTerm}
|
|
onSearchChange={handleSearchChange}
|
|
placeholder="Search by project name, WP, plot, or investment number..."
|
|
resultsCount={filteredProjects.length}
|
|
resultsText="projects"
|
|
/>
|
|
{filteredProjects.length === 0 && searchTerm ? (
|
|
<Card>
|
|
<CardContent className="text-center py-12">
|
|
<div className="text-gray-400 mb-4">
|
|
<svg
|
|
className="w-16 h-16 mx-auto"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
No projects found
|
|
</h3>
|
|
<p className="text-gray-500 mb-6">
|
|
No projects match your search criteria. Try adjusting your search
|
|
terms.
|
|
</p>
|
|
<Button variant="outline" onClick={() => setSearchTerm("")}>
|
|
Clear Search
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
) : projects.length === 0 ? (
|
|
<Card>
|
|
<CardContent className="text-center py-12">
|
|
<div className="text-gray-400 mb-4">
|
|
<svg
|
|
className="w-16 h-16 mx-auto"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V8zm0 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
No projects yet
|
|
</h3>
|
|
<p className="text-gray-500 mb-6">
|
|
Get started by creating your first project
|
|
</p>
|
|
<Link href="/projects/new">
|
|
<Button variant="primary">Create First Project</Button>
|
|
</Link>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
<table className="w-full table-fixed">
|
|
<thead>
|
|
<tr className="bg-gray-100 border-b">
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-16">
|
|
No.
|
|
</th>
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700">
|
|
Project Name
|
|
</th>
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
|
WP
|
|
</th>
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
|
City
|
|
</th>
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-32">
|
|
Address
|
|
</th>
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
|
Plot
|
|
</th>
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-24">
|
|
Finish
|
|
</th>
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-12">
|
|
Type
|
|
</th>
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-24">
|
|
Status
|
|
</th>{" "}
|
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{filteredProjects.map((project, index) => (
|
|
<tr
|
|
key={project.project_id}
|
|
className={`border-b hover:bg-gray-50 transition-colors ${
|
|
index % 2 === 0 ? "bg-white" : "bg-gray-25"
|
|
}`}
|
|
>
|
|
<td className="px-2 py-3">
|
|
<Badge variant="primary" size="sm" className="text-xs">
|
|
{project.project_number}
|
|
</Badge>
|
|
</td>
|
|
<td className="px-2 py-3">
|
|
<Link
|
|
href={`/projects/${project.project_id}`}
|
|
className="font-medium text-blue-600 hover:text-blue-800 transition-colors text-sm truncate block"
|
|
title={project.project_name}
|
|
>
|
|
{project.project_name}
|
|
</Link>
|
|
</td>
|
|
<td
|
|
className="px-2 py-3 text-xs text-gray-600 truncate"
|
|
title={project.wp}
|
|
>
|
|
{project.wp || "N/A"}
|
|
</td>
|
|
<td
|
|
className="px-2 py-3 text-xs text-gray-600 truncate"
|
|
title={project.city}
|
|
>
|
|
{project.city || "N/A"}
|
|
</td>
|
|
<td
|
|
className="px-2 py-3 text-xs text-gray-600 truncate"
|
|
title={project.address}
|
|
>
|
|
{project.address || "N/A"}
|
|
</td>
|
|
<td
|
|
className="px-2 py-3 text-xs text-gray-600 truncate"
|
|
title={project.plot}
|
|
>
|
|
{project.plot || "N/A"}
|
|
</td>{" "}
|
|
<td
|
|
className="px-2 py-3 text-xs text-gray-600 truncate"
|
|
title={project.finish_date}
|
|
>
|
|
{project.finish_date
|
|
? formatDate(project.finish_date)
|
|
: "N/A"}
|
|
</td>
|
|
<td className="px-2 py-3 text-xs text-gray-600 text-center font-semibold">
|
|
{project.project_type === "design"
|
|
? "P"
|
|
: project.project_type === "construction"
|
|
? "R"
|
|
: project.project_type === "design+construction"
|
|
? "P+R"
|
|
: "-"}
|
|
</td>
|
|
<td className="px-2 py-3 text-xs text-gray-600 truncate">
|
|
{project.project_status === "registered"
|
|
? "Zarejestr."
|
|
: project.project_status === "in_progress_design"
|
|
? "W real. (P)"
|
|
: project.project_status === "in_progress_construction"
|
|
? "W real. (R)"
|
|
: project.project_status === "fulfilled"
|
|
? "Zakończony"
|
|
: "-"}
|
|
</td>
|
|
<td className="px-2 py-3">
|
|
<Link href={`/projects/${project.project_id}`}>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="text-xs px-2 py-1"
|
|
>
|
|
View
|
|
</Button>
|
|
</Link>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</PageContainer>
|
|
);
|
|
}
|