134 lines
8.4 KiB
TypeScript
134 lines
8.4 KiB
TypeScript
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
|
|
import { Head, Link, router, usePage } from '@inertiajs/react';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/Components/ui/select';
|
|
import { Badge } from '@/Components/ui/badge';
|
|
import { Button } from '@/Components/ui/button';
|
|
import { Tabs, TabsList, TabsTrigger } from '@/Components/ui/tabs';
|
|
import { ArrowLeft, Timer, Package, ClipboardList, Play, CheckCircle2, ArrowRight } from 'lucide-react';
|
|
|
|
export default function ProjectLayout({ project, allowedTransitions, children, currentTab }: { project: any, allowedTransitions?: any[], children: React.ReactNode, currentTab: string }) {
|
|
const params = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '');
|
|
const backTo = params.get('back_to');
|
|
|
|
const handleBack = (e: React.MouseEvent) => {
|
|
if (backTo === 'inventory') {
|
|
router.visit(route('inventory.index'));
|
|
} else if (typeof window !== 'undefined') {
|
|
if (window.history.length > 1) {
|
|
window.history.back();
|
|
} else {
|
|
router.visit(route('projects.index'));
|
|
}
|
|
}
|
|
};
|
|
|
|
const statusLabel = (s: string) => s?.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) || '';
|
|
|
|
const handleTransition = (status: string) => {
|
|
router.patch(route('projects.transition', project.ulid), { status });
|
|
};
|
|
|
|
const handleTabChange = (val: string) => {
|
|
// Kept for backward compatibility if needed, but tabs are removed
|
|
};
|
|
|
|
const { projects, projectOptions } = usePage<any>().props;
|
|
const selectableProjects = projectOptions ?? projects;
|
|
|
|
return (
|
|
<AuthenticatedLayout
|
|
header={
|
|
project ? (
|
|
<div className="flex items-center justify-between w-full">
|
|
<div className="flex items-center gap-4">
|
|
<Button variant="ghost" size="icon-sm" onClick={handleBack}>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</Button>
|
|
<div>
|
|
<div className="flex items-baseline gap-2">
|
|
<h2 className="text-xl font-semibold leading-tight text-gray-800">
|
|
{project.name}
|
|
</h2>
|
|
<Badge variant="outline" className="capitalize shrink-0">{statusLabel(project.status)}</Badge>
|
|
</div>
|
|
<p className="text-sm text-gray-500 mt-0.5">{project.code}</p>
|
|
</div>
|
|
</div>
|
|
{allowedTransitions && allowedTransitions.length > 0 && (
|
|
<div className="flex items-center gap-2">
|
|
{allowedTransitions.map((t: any) => (
|
|
<Button key={t.value} variant="outline" size="sm" onClick={() => handleTransition(t.value)}>
|
|
{t.value === 'in_progress' && <Play className="mr-1 h-3 w-3" />}
|
|
{t.value === 'completed' && <CheckCircle2 className="mr-1 h-3 w-3" />}
|
|
{t.label}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : undefined
|
|
}
|
|
>
|
|
<Head title={project ? `${project.name} - ${currentTab.charAt(0).toUpperCase() + currentTab.slice(1)}` : 'Select Project'} />
|
|
|
|
<div className={currentTab === 'tasks' ? "h-[calc(100vh-73px)] flex flex-col pt-2" : "py-6"}>
|
|
<div className={currentTab === 'tasks' ? "w-full h-full p-4 flex flex-col" : "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"}>
|
|
{!project ? (
|
|
<div className="py-10">
|
|
<div className="text-center mb-8">
|
|
<h3 className="text-2xl font-semibold text-gray-900">Select a Project</h3>
|
|
<p className="mt-2 text-sm text-gray-500">Choose a project to view its {currentTab.replace('-', ' ')} data.</p>
|
|
</div>
|
|
|
|
{selectableProjects && selectableProjects.length > 0 ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
|
{selectableProjects.map((p: any) => (
|
|
<div
|
|
key={p.ulid}
|
|
onClick={() => {
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('project', p.ulid);
|
|
window.location.href = url.toString();
|
|
}}
|
|
className="group relative flex flex-col items-start justify-between rounded-xl border border-gray-200 bg-white p-6 shadow-sm transition-all hover:border-emerald-500 hover:shadow-md cursor-pointer overflow-hidden"
|
|
>
|
|
<div className="absolute inset-x-0 top-0 h-1 bg-transparent transition-colors group-hover:bg-emerald-500" />
|
|
<div className="flex w-full items-start justify-between gap-4 mb-6">
|
|
<div className="flex-1">
|
|
<h4 className="font-semibold text-lg text-gray-900 group-hover:text-emerald-700 transition-colors line-clamp-2">{p.name}</h4>
|
|
<div className="flex flex-wrap items-center gap-2 mt-1">
|
|
<span className="text-sm font-mono text-gray-500">{p.code}</span>
|
|
{p.project_type === 'extension' && p.parent_project && (
|
|
<Badge variant="secondary" className="text-[10px] py-0 px-1.5 font-normal bg-slate-100 text-slate-700 hover:bg-slate-100">
|
|
Ext of {p.parent_project.code}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{p.status && <Badge variant="outline" className="shrink-0 capitalize">{statusLabel(p.status)}</Badge>}
|
|
</div>
|
|
<div className="mt-auto flex w-full items-center justify-between">
|
|
<span className="text-xs text-gray-400">Click to select</span>
|
|
<div className="flex items-center text-sm font-medium text-emerald-600 opacity-0 transition-opacity group-hover:opacity-100">
|
|
Open <ArrowRight className="ml-1 h-4 w-4" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-20 bg-white shadow-sm sm:rounded-lg border border-dashed border-gray-300 max-w-3xl mx-auto">
|
|
<h3 className="text-lg font-medium text-gray-900">No projects found</h3>
|
|
<p className="mt-1 text-sm text-gray-500">There are no active projects available for your account.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
children
|
|
)}
|
|
</div>
|
|
</div>
|
|
</AuthenticatedLayout>
|
|
);
|
|
}
|