119 lines
6.6 KiB
TypeScript
119 lines
6.6 KiB
TypeScript
import React, { FormEvent } from 'react';
|
|
import { InertiaFormProps } from '@inertiajs/react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/Components/ui/card';
|
|
import { Button } from '@/Components/ui/button';
|
|
import { Input } from '@/Components/ui/input';
|
|
import { Badge } from '@/Components/ui/badge';
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/Components/ui/dialog';
|
|
import { Users, Plus, Trash2, Mail } from 'lucide-react';
|
|
import { ProjectData, PersonnelItem } from '../../../types/project-show';
|
|
|
|
interface TeamTabProps {
|
|
project: ProjectData;
|
|
employees: { id: number; ulid: string; name: string }[];
|
|
memberForm: InertiaFormProps<{ user_id: string; role: string }>;
|
|
handleAddMember: (e: FormEvent) => void;
|
|
handleRemoveMember: (userUlid: string) => void;
|
|
statusLabel: (s: string) => string;
|
|
}
|
|
|
|
export default function TeamTab({
|
|
project,
|
|
employees,
|
|
memberForm,
|
|
handleAddMember,
|
|
handleRemoveMember,
|
|
statusLabel
|
|
}: TeamTabProps) {
|
|
const personnel = project.personnel || [];
|
|
const existingPersonnelUlids = new Set(personnel.map((p: PersonnelItem) => p.ulid));
|
|
const availableEmployees = employees.filter((e) => !existingPersonnelUlids.has(e.ulid));
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Project Personnel / Team Members */}
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between">
|
|
<div>
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Users className="h-5 w-5 text-blue-500" /> Assigned Personnel ({personnel.length})
|
|
</CardTitle>
|
|
<p className="text-xs text-gray-400 mt-1">Team members allocated to work on {project.name}</p>
|
|
</div>
|
|
<Dialog>
|
|
<DialogTrigger render={<Button size="sm" />}>
|
|
<Plus className="mr-2 h-4 w-4" /> Add Team Member
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader><DialogTitle>Add Personnel to Project</DialogTitle></DialogHeader>
|
|
<form onSubmit={handleAddMember} className="space-y-4">
|
|
<div>
|
|
<label className="text-sm font-medium">Select Employee *</label>
|
|
<select
|
|
value={memberForm.data.user_id}
|
|
onChange={e => memberForm.setData('user_id', e.target.value)}
|
|
required
|
|
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
|
|
>
|
|
<option value="">Choose an employee...</option>
|
|
{availableEmployees.map((emp) => (
|
|
<option key={emp.ulid} value={emp.ulid}>{emp.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-sm font-medium">Project Role</label>
|
|
<Input
|
|
value={memberForm.data.role}
|
|
onChange={e => memberForm.setData('role', e.target.value)}
|
|
placeholder="e.g. Lead Engineer, Safety Officer"
|
|
/>
|
|
</div>
|
|
<div className="flex justify-end">
|
|
<Button type="submit" disabled={memberForm.processing}>
|
|
<Plus className="mr-2 h-4 w-4" /> Add Member
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{personnel.length === 0 ? (
|
|
<p className="text-sm text-gray-400 italic py-6 text-center">No additional personnel assigned. Add team members to collaborate on tasks.</p>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
{personnel.map((member: PersonnelItem) => (
|
|
<div key={member.ulid} className="flex items-center justify-between p-3 rounded-lg border bg-white hover:bg-gray-50/50 transition-colors">
|
|
<div className="flex items-center gap-3 min-w-0">
|
|
<div className="h-9 w-9 rounded-full bg-slate-100 text-slate-700 font-semibold flex items-center justify-center text-xs shrink-0">
|
|
{member.name.charAt(0)}
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="font-medium text-xs text-gray-900 truncate">{member.name}</p>
|
|
<p className="text-[11px] text-gray-400 truncate">{member.email}</p>
|
|
{member.pivot?.role && (
|
|
<Badge variant="outline" className="text-[9px] px-1 py-0 mt-1 font-normal text-slate-600">
|
|
{member.pivot.role}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="ghost" size="icon-sm"
|
|
title="Remove member"
|
|
className="text-gray-400 hover:text-red-600"
|
|
onClick={() => handleRemoveMember(member.ulid)}
|
|
>
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|