refactor: redesign table module
This commit is contained in:
@@ -12,6 +12,8 @@ import { PageHeader } from "@/components/page-header"
|
||||
import { createProject, fetchProjects, updateProject, deleteProject, type ProjectCreate, type Project, type ProjectUpdate } from "@/lib/api"
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
import { toast } from "sonner"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { PoweredBy } from "@/components/powered-by"
|
||||
|
||||
export default function ProjectPage() {
|
||||
@@ -91,7 +93,9 @@ export default function ProjectPage() {
|
||||
end_lng: endLng ? parseFloat(endLng) : null,
|
||||
}
|
||||
await updateProject(currentProject.id, data)
|
||||
toast.success("Project updated successfully!")
|
||||
toast.success("Project Updated", {
|
||||
description: `${name} has been updated successfully at ${new Date().toLocaleTimeString()}`,
|
||||
})
|
||||
} else {
|
||||
const data: ProjectCreate = {
|
||||
name: name.trim(),
|
||||
@@ -103,7 +107,9 @@ export default function ProjectPage() {
|
||||
end_lng: endLng ? parseFloat(endLng) : null,
|
||||
}
|
||||
await createProject(data)
|
||||
toast.success("Project created successfully!")
|
||||
toast.success("Project Created", {
|
||||
description: `${name} has been established successfully at ${new Date().toLocaleTimeString()}`,
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh projects list
|
||||
@@ -113,7 +119,11 @@ export default function ProjectPage() {
|
||||
setIsModalOpen(false)
|
||||
resetForm()
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : `Failed to ${isEditing ? 'update' : 'create'} project`)
|
||||
const message = err instanceof Error ? err.message : `Failed to ${isEditing ? 'update' : 'create'} project`
|
||||
setError(message)
|
||||
toast.error("Operation Failed", {
|
||||
description: message,
|
||||
})
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
@@ -138,10 +148,15 @@ export default function ProjectPage() {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
await deleteProject(project.id)
|
||||
toast.success("Project deleted successfully!")
|
||||
toast.success("Project Deleted", {
|
||||
description: `${project.name} has been removed from the system.`,
|
||||
})
|
||||
await loadProjects()
|
||||
} catch (err) {
|
||||
setError("Failed to delete project")
|
||||
toast.error("Deletion Failed", {
|
||||
description: "The project could not be removed. Please try again or check your permissions.",
|
||||
})
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
@@ -152,7 +167,7 @@ export default function ProjectPage() {
|
||||
accessorKey: "name",
|
||||
header: "Project Name",
|
||||
cell: ({ row }) => (
|
||||
<div className="font-semibold text-gray-900 dark:text-gray-100">{row.original.name}</div>
|
||||
<div className="font-semibold">{row.original.name}</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -160,17 +175,45 @@ export default function ProjectPage() {
|
||||
header: "State",
|
||||
cell: ({ row }) => {
|
||||
const project = row.original
|
||||
if (!project.state) return <span className="text-gray-400">—</span>
|
||||
if (!project.state) return <span>—</span>
|
||||
|
||||
const states = project.state.split(',').map(s => s.trim()).filter(Boolean)
|
||||
if (states.length === 0) return <span>—</span>
|
||||
|
||||
const firstState = states[0]
|
||||
const remainingStates = states.slice(1)
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{project.state.split(',').map((item, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className="px-2 py-0.5 rounded-full bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-semibold border border-blue-100 dark:border-blue-800"
|
||||
>
|
||||
{item.trim()}
|
||||
</span>
|
||||
))}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
|
||||
>
|
||||
{firstState}
|
||||
</Badge>
|
||||
|
||||
{remainingStates.length > 0 && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="flex items-center justify-center rounded-full bg-muted/50 hover:bg-muted px-1.5 py-0.5 text-[10px] font-bold text-muted-foreground transition-colors border border-border/40">
|
||||
+{remainingStates.length}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-2" align="start">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<p className="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-0.5 px-1">Other States</p>
|
||||
{remainingStates.map((item, idx) => (
|
||||
<Badge
|
||||
key={idx}
|
||||
variant="secondary"
|
||||
>
|
||||
{item}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -182,27 +225,26 @@ export default function ProjectPage() {
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<main className="min-h-screen relative overflow-hidden">
|
||||
<div className="mx-auto px-6 py-8 max-w-340 relative z-10">
|
||||
<>
|
||||
|
||||
<main className="relative z-10">
|
||||
{/* Refined Header */}
|
||||
<div className="mb-8">
|
||||
<PageHeader
|
||||
title="Project Management"
|
||||
description="Manage road infrastructure projects"
|
||||
icon={FolderPlus}
|
||||
actions={
|
||||
<Button
|
||||
onClick={() => { setIsEditing(false); setIsModalOpen(true); }}
|
||||
>
|
||||
<FolderPlus className="mr-2 h-5 w-5" />
|
||||
Add New Project
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
{error && !isModalOpen && (
|
||||
<div className="mb-6 flex items-center gap-3 p-4 rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800">
|
||||
<div className="w-5 h-5 rounded-full bg-red-100 dark:bg-red-900 flex items-center justify-center shrink-0">
|
||||
<span className="text-xs font-bold text-red-500">!</span>
|
||||
</div>
|
||||
<p className="text-sm text-red-600 dark:text-red-400 break-all">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Data Table */}
|
||||
<div>
|
||||
@@ -210,10 +252,8 @@ export default function ProjectPage() {
|
||||
title="Projects"
|
||||
data={projects}
|
||||
columns={columns}
|
||||
onAddNew={() => { setIsEditing(false); setIsModalOpen(true); }}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
addButtonText="Add New Project"
|
||||
isLoading={isLoading}
|
||||
pagination={{
|
||||
skip,
|
||||
@@ -228,8 +268,8 @@ export default function ProjectPage() {
|
||||
</div>
|
||||
|
||||
<PoweredBy />
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
|
||||
{/* Modal Dialog */}
|
||||
<Dialog open={isModalOpen} onOpenChange={(open) => { if (!open) { setIsModalOpen(false); resetForm(); } else { setIsModalOpen(true); } }}>
|
||||
@@ -250,15 +290,6 @@ export default function ProjectPage() {
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
{/* Error Message in Modal */}
|
||||
{error && (
|
||||
<div className="flex items-center gap-3 p-4 rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800">
|
||||
<div className="w-5 h-5 rounded-full bg-red-100 dark:bg-red-900 flex items-center justify-center shrink-0">
|
||||
<span className="text-xs font-bold text-red-500">!</span>
|
||||
</div>
|
||||
<p className="text-sm text-red-600 dark:text-red-400 break-all">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Project Name */}
|
||||
@@ -409,6 +440,6 @@ export default function ProjectPage() {
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user