133 lines
7.3 KiB
TypeScript
133 lines
7.3 KiB
TypeScript
import ProjectLayout from '../../Layouts/ProjectLayout';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/Components/ui/card';
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/Components/ui/table';
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/Components/ui/dialog';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/Components/ui/select';
|
|
import { Form, FormField } from '@/Components/ui/form';
|
|
import { Button } from '@/Components/ui/button';
|
|
import { Badge } from '@/Components/ui/badge';
|
|
import { UserPlus, UserMinus } from 'lucide-react';
|
|
import { useForm, router } from '@inertiajs/react';
|
|
import { FormEvent, useMemo, useState } from 'react';
|
|
import { PageProps } from '@/types';
|
|
|
|
interface PersonnelItem {
|
|
id: number; ulid: string; name: string; email: string;
|
|
pivot?: { role: string };
|
|
}
|
|
|
|
interface ProjectData {
|
|
id: number; ulid: string; name: string; code: string; status: string;
|
|
personnel: PersonnelItem[];
|
|
}
|
|
|
|
interface Props extends PageProps {
|
|
project: ProjectData;
|
|
employees: { id: number; ulid: string; name: string }[];
|
|
}
|
|
|
|
const statusLabel = (s: string) => s.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
const roleItems = [{ value: 'pm', label: 'Project Manager' }, { value: 'engineer', label: 'Engineer' }, { value: 'laborer', label: 'Laborer' }, { value: 'member', label: 'Member' }];
|
|
|
|
export default function Team({ project, employees }: Props) {
|
|
const [addMemberOpen, setAddMemberOpen] = useState(false);
|
|
const memberForm = useForm({ user_id: '', role: 'member' });
|
|
|
|
const employeeSelectItems = useMemo(() => employees.map(e => ({ value: e.ulid, label: e.name })), [employees]);
|
|
|
|
const handleAddMember = (e: FormEvent) => {
|
|
e.preventDefault();
|
|
memberForm.post(route('projects.personnel.add', project.ulid), {
|
|
onSuccess: () => { memberForm.reset(); setAddMemberOpen(false); },
|
|
});
|
|
};
|
|
|
|
const handleRemoveMember = (userUlid: string) => {
|
|
if (confirm('Remove this team member?')) {
|
|
router.delete(route('projects.personnel.remove', [project.ulid, userUlid]));
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ProjectLayout project={project} currentTab="team">
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle>Team Members</CardTitle>
|
|
<Dialog open={addMemberOpen} onOpenChange={setAddMemberOpen}>
|
|
<DialogTrigger>
|
|
<Button size="sm">
|
|
<UserPlus className="mr-2 h-4 w-4" /> Add Member
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader><DialogTitle>Add Team Member</DialogTitle></DialogHeader>
|
|
<Form onSubmit={handleAddMember}>
|
|
<FormField label="Employee" required>
|
|
<Select value={memberForm.data.user_id} onValueChange={(v) => { if (v) memberForm.setData('user_id', v); }} items={employeeSelectItems}>
|
|
<SelectTrigger><SelectValue placeholder="Select employee" /></SelectTrigger>
|
|
<SelectContent>
|
|
{employees.map((e) => (
|
|
<SelectItem key={e.id} value={e.ulid}>{e.name}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</FormField>
|
|
<FormField label="Role">
|
|
<Select value={memberForm.data.role} onValueChange={(v) => { if (v) memberForm.setData('role', v); }} items={roleItems}>
|
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="pm">Project Manager</SelectItem>
|
|
<SelectItem value="engineer">Engineer</SelectItem>
|
|
<SelectItem value="laborer">Laborer</SelectItem>
|
|
<SelectItem value="member">Member</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</FormField>
|
|
<div className="flex justify-end">
|
|
<Button type="submit" disabled={memberForm.processing}><UserPlus className="mr-2 h-4 w-4" /> Add</Button>
|
|
</div>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Name</TableHead>
|
|
<TableHead>Email</TableHead>
|
|
<TableHead>Role</TableHead>
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{project.personnel.length === 0 ? (
|
|
<TableRow><TableCell colSpan={4} className="text-center text-gray-500 py-8">No team members assigned.</TableCell></TableRow>
|
|
) : (
|
|
project.personnel.map((member) => (
|
|
<TableRow key={member.id}>
|
|
<TableCell className="font-medium">{member.name}</TableCell>
|
|
<TableCell className="text-gray-500">{member.email}</TableCell>
|
|
<TableCell>
|
|
<Badge variant={member.pivot?.role === 'pm' ? 'default' : 'outline'}>
|
|
{member.pivot?.role === 'pm' ? 'Project Manager' : statusLabel(member.pivot?.role || 'member')}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<Button variant="ghost" size="icon-sm" title="Remove" onClick={() => handleRemoveMember(member.ulid)}>
|
|
<UserMinus className="h-4 w-4 text-red-500" />
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</ProjectLayout>
|
|
);
|
|
}
|