Files
GSB-Construction/Modules/UserManagement/resources/js/Pages/Users/Edit.tsx

328 lines
20 KiB
TypeScript

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link, useForm, usePage, router } from '@inertiajs/react';
import { Button } from '@/Components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/Components/ui/card';
import { Input } from '@/Components/ui/input';
import { Label } from '@/Components/ui/label';
import {
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
} from '@/Components/ui/select';
import { ArrowLeft, Building2, Save, Unlink } from 'lucide-react';
import { Contractor, User, PageProps } from '@/types';
import { FormEvent } from 'react';
interface Props extends PageProps {
user: User;
contractors: Contractor[];
isPlatformAdmin: boolean;
}
export default function Edit({ user, contractors, isPlatformAdmin }: Props) {
const { data, setData, put, processing, errors } = useForm({
name: user.name,
email: user.email,
password: '',
password_confirmation: '',
user_type: user.user_type,
status: user.status,
department: user.employee_profile?.department || '',
position: user.employee_profile?.position || '',
employee_code: user.employee_profile?.employee_code || '',
hire_date: user.employee_profile?.hire_date
? user.employee_profile.hire_date.slice(0, 10)
: '',
phone: (user.employee_profile?.phone || user.customer_profile?.phone) || '',
address: (user.employee_profile?.address || user.customer_profile?.address) || '',
company_name: user.customer_profile?.company_name || '',
contact_person: user.customer_profile?.contact_person || '',
tin_number: user.customer_profile?.tin_number || '',
spatie_role: user.roles?.[0]?.name || user.user_type,
});
const contractorForm = useForm({
contractor_id: user.contractor_id ? String(user.contractor_id) : 'none',
});
const handleLinkContractor = (e: FormEvent) => {
e.preventDefault();
contractorForm.patch(route('users.link-contractor', user.ulid));
};
const handleUnlink = () => {
if (!confirm('Remove contractor link from this user?')) return;
contractorForm.setData('contractor_id', 'none');
router.patch(route('users.link-contractor', user.ulid), {
contractor_id: null,
});
};
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
put(route('users.update', user.ulid));
};
const isEmployee = data.user_type === 'admin' || data.user_type === 'employee';
const isCustomer = data.user_type === 'customer';
return (
<AuthenticatedLayout
header={
<div className="flex items-center gap-4">
<Link href={route('users.index')}>
<Button variant="ghost" size="icon-sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<h2 className="text-xl font-semibold leading-tight text-gray-800">
Edit User: {user.name}
</h2>
</div>
}
>
<Head title={`Edit ${user.name}`} />
<div className="py-6">
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
<form onSubmit={handleSubmit}>
{Object.keys(errors).length > 0 && (
<div className="rounded-md bg-red-50 p-4 border border-red-200 mb-6">
<div className="flex">
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">There were validation errors with your submission</h3>
<div className="mt-2 text-sm text-red-700">
<ul className="list-disc space-y-1 pl-5">
{Object.entries(errors).map(([field, message]) => (
<li key={field}>{String(message)}</li>
))}
</ul>
</div>
</div>
</div>
</div>
)}
<Card>
<CardHeader>
<CardTitle>Account Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="name">Full Name *</Label>
<Input id="name" value={data.name} onChange={(e) => setData('name', e.target.value)} />
{errors.name && <p className="mt-1 text-sm text-red-500">{errors.name}</p>}
</div>
<div>
<Label htmlFor="email">Email *</Label>
<Input id="email" type="email" value={data.email} onChange={(e) => setData('email', e.target.value)} />
{errors.email && <p className="mt-1 text-sm text-red-500">{errors.email}</p>}
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="password">New Password</Label>
<Input id="password" type="password" value={data.password} onChange={(e) => setData('password', e.target.value)} placeholder="Leave blank to keep current" />
{errors.password && <p className="mt-1 text-sm text-red-500">{errors.password}</p>}
</div>
<div>
<Label htmlFor="password_confirmation">Confirm New Password</Label>
<Input id="password_confirmation" type="password" value={data.password_confirmation} onChange={(e) => setData('password_confirmation', e.target.value)} />
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label>User Type *</Label>
<Select value={data.user_type} onValueChange={(v) => {
if (v) {
setData('user_type', v);
if (v !== 'contractor') setData('spatie_role', v);
}
}}>
<SelectTrigger id="user_type"><SelectValue /></SelectTrigger>
<SelectContent>
{isPlatformAdmin && <SelectItem value="admin">Admin</SelectItem>}
<SelectItem value="employee">Employee</SelectItem>
<SelectItem value="contractor">Contractor</SelectItem>
</SelectContent>
</Select>
</div>
{data.user_type === 'contractor' && (
<div>
<Label>Role Level *</Label>
<Select value={data.spatie_role} onValueChange={(v) => { if (v) setData('spatie_role', v); }}>
<SelectTrigger id="spatie_role"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="main-contractor-admin">Main Contractor Admin</SelectItem>
<SelectItem value="main-contractor-user">Main Contractor User</SelectItem>
<SelectItem value="contractor">Sub Contractor</SelectItem>
</SelectContent>
</Select>
</div>
)}
<div>
<Label>Status *</Label>
<Select value={data.status} onValueChange={(v) => { if (v) setData('status', v); }} items={[{ value: 'active', label: 'Active' }, { value: 'inactive', label: 'Inactive' }, { value: 'suspended', label: 'Suspended' }]}>
<SelectTrigger id="status"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
<SelectItem value="suspended">Suspended</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
{isEmployee && (
<Card className="mt-6">
<CardHeader><CardTitle>Employee Profile</CardTitle></CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="department">Department</Label>
<Input id="department" value={data.department} onChange={(e) => setData('department', e.target.value)} />
</div>
<div>
<Label htmlFor="position">Position</Label>
<Input id="position" value={data.position} onChange={(e) => setData('position', e.target.value)} />
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="employee_code">Employee Code</Label>
<Input id="employee_code" value={data.employee_code} onChange={(e) => setData('employee_code', e.target.value)} />
{errors.employee_code && <p className="mt-1 text-sm text-red-500">{errors.employee_code}</p>}
</div>
<div>
<Label htmlFor="hire_date">Hire Date</Label>
<Input id="hire_date" type="date" value={data.hire_date} onChange={(e) => setData('hire_date', e.target.value)} />
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="phone">Phone</Label>
<Input id="phone" value={data.phone} onChange={(e) => setData('phone', e.target.value)} />
</div>
</div>
<div>
<Label htmlFor="address">Address</Label>
<Input id="address" value={data.address} onChange={(e) => setData('address', e.target.value)} />
</div>
</CardContent>
</Card>
)}
{isCustomer && (
<Card className="mt-6">
<CardHeader><CardTitle>Customer Profile</CardTitle></CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="company_name">Company Name</Label>
<Input id="company_name" value={data.company_name} onChange={(e) => setData('company_name', e.target.value)} />
</div>
<div>
<Label htmlFor="contact_person">Contact Person</Label>
<Input id="contact_person" value={data.contact_person} onChange={(e) => setData('contact_person', e.target.value)} />
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="tin_number">TIN Number</Label>
<Input id="tin_number" value={data.tin_number} onChange={(e) => setData('tin_number', e.target.value)} />
</div>
<div>
<Label htmlFor="phone">Phone</Label>
<Input id="phone" value={data.phone} onChange={(e) => setData('phone', e.target.value)} />
</div>
</div>
<div>
<Label htmlFor="address">Address</Label>
<Input id="address" value={data.address} onChange={(e) => setData('address', e.target.value)} />
</div>
</CardContent>
</Card>
)}
<div className="mt-6 flex justify-end gap-3">
<Link href={route('users.index')}>
<Button variant="outline" type="button">Cancel</Button>
</Link>
<Button type="submit" disabled={processing}>
<Save className="mr-2 h-4 w-4" /> Update User
</Button>
</div>
</form>
{/* ── Contractor Link — MUST be outside main form (no nested forms) ── */}
{isPlatformAdmin && contractors.length > 0 && (
<Card className="mt-6 border-blue-200 bg-blue-50/40">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-base text-blue-800">
<Building2 className="h-4 w-4" />
Contractor Link
</CardTitle>
<p className="text-sm text-blue-600">
Link this user to a contractor so they can access the bidding portal.
</p>
</CardHeader>
<CardContent>
{user.contractor && (
<div className="mb-3 flex items-center justify-between rounded-md bg-white border px-3 py-2">
<div className="flex items-center gap-2 text-sm">
<Building2 className="h-4 w-4 text-blue-500" />
<span className="font-medium">{(user.contractor as any).company_name}</span>
<span className="text-xs text-gray-400 capitalize">({(user.contractor as any).type})</span>
</div>
<Button
type="button"
size="sm"
variant="ghost"
className="text-red-500 hover:text-red-700"
onClick={handleUnlink}
disabled={contractorForm.processing}
>
<Unlink className="mr-1 h-3.5 w-3.5" /> Remove
</Button>
</div>
)}
<form onSubmit={handleLinkContractor} className="flex gap-2">
<div className="flex-1">
<Select
value={contractorForm.data.contractor_id}
onValueChange={(v) => contractorForm.setData('contractor_id', v || 'none')}
>
<SelectTrigger>
<SelectValue placeholder="— Select contractor —">
{contractorForm.data.contractor_id && contractorForm.data.contractor_id !== 'none'
? contractors.find(c => String(c.id) === contractorForm.data.contractor_id)?.company_name ?? contractorForm.data.contractor_id
: null}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> No contractor </SelectItem>
{contractors.map(c => (
<SelectItem key={c.id} value={String(c.id)}>
{c.company_name}
<span className="ml-2 text-xs text-gray-400 capitalize">({c.type})</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Button type="submit" size="sm" disabled={contractorForm.processing}>
<Save className="mr-1 h-3.5 w-3.5" />
{user.contractor ? 'Update' : 'Link'}
</Button>
</form>
</CardContent>
</Card>
)}
</div>
</div>
</AuthenticatedLayout>
);
}