Files
GSB-Construction/Modules/ProjectManagement/resources/js/Pages/Reports/Create.tsx

260 lines
15 KiB
TypeScript

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link, useForm } 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 { Separator } from '@/Components/ui/separator';
import { ArrowLeft, Save, Send, HardHat, ShieldCheck, FileText } from 'lucide-react';
import { PageProps } from '@/types';
import { FormEvent } from 'react';
interface Props extends PageProps {
project: { id: number; ulid: string; name: string; code: string };
defaultPeriodStart: string;
defaultPeriodEnd: string;
}
export default function Create({ project, defaultPeriodStart, defaultPeriodEnd }: Props) {
const form = useForm({
period_start: defaultPeriodStart,
period_end: defaultPeriodEnd,
// Workforce
active_workforce: '',
period_man_hours: '',
logistics_km: '',
// HSE Proactive
toolbox_meetings: '',
safety_observations: '',
// HSE Reactive
fatalities: '0',
major_injuries: '0',
first_aid_cases: '0',
medical_cases: '0',
near_misses: '0',
environmental_damage: '0',
property_damage: '0',
fines: '0',
// Narrative
narrative_status: '',
narrative_weather: '',
narrative_compliance: '',
});
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
form.post(route('projects.reports.store', project.ulid));
};
const fieldError = (field: string) => {
const err = (form.errors as Record<string, string>)[field];
return err ? <p className="mt-1 text-sm text-red-500">{err}</p> : null;
};
return (
<AuthenticatedLayout
header={
<div className="flex items-center gap-4">
<Link href={route('projects.reports.index', project.ulid)}>
<Button variant="ghost" size="icon-sm"><ArrowLeft className="h-4 w-4" /></Button>
</Link>
<div>
<h2 className="text-xl font-semibold leading-tight text-gray-800">New Status Report</h2>
<p className="text-sm text-gray-500">{project.name} ({project.code})</p>
</div>
</div>
}
>
<Head title={`New Report - ${project.name}`} />
<div className="py-6">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Period */}
<Card>
<CardHeader>
<CardTitle className="text-base">Reporting Period</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="period_start">Period Start *</Label>
<Input id="period_start" type="date" value={form.data.period_start}
onChange={e => form.setData('period_start', e.target.value)} />
{fieldError('period_start')}
</div>
<div>
<Label htmlFor="period_end">Period End *</Label>
<Input id="period_end" type="date" value={form.data.period_end}
onChange={e => form.setData('period_end', e.target.value)} />
{fieldError('period_end')}
</div>
</div>
</CardContent>
</Card>
{/* Workforce */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<HardHat className="h-5 w-5 text-amber-600" />
Workforce & Productivity
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<Label htmlFor="active_workforce">Active Workforce</Label>
<Input id="active_workforce" type="number" min="0" placeholder="e.g. 78"
value={form.data.active_workforce}
onChange={e => form.setData('active_workforce', e.target.value)} />
{fieldError('active_workforce')}
</div>
<div>
<Label htmlFor="period_man_hours">Period Man-hours</Label>
<Input id="period_man_hours" type="number" min="0" step="0.01" placeholder="e.g. 4368"
value={form.data.period_man_hours}
onChange={e => form.setData('period_man_hours', e.target.value)} />
{fieldError('period_man_hours')}
</div>
<div>
<Label htmlFor="logistics_km">Logistics (km driven)</Label>
<Input id="logistics_km" type="number" min="0" step="0.01" placeholder="e.g. 25200"
value={form.data.logistics_km}
onChange={e => form.setData('logistics_km', e.target.value)} />
{fieldError('logistics_km')}
</div>
</div>
<p className="mt-3 text-xs text-gray-400">
Cumulative man-hours will be calculated automatically from previous approved reports.
</p>
</CardContent>
</Card>
{/* HSE */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<ShieldCheck className="h-5 w-5 text-emerald-600" />
HSE Performance
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Proactive */}
<div>
<h4 className="text-sm font-medium text-gray-700 mb-3">Proactive Safety Measures</h4>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<Label htmlFor="toolbox_meetings">Toolbox / Tailgate Meetings</Label>
<Input id="toolbox_meetings" type="number" min="0" placeholder="e.g. 6"
value={form.data.toolbox_meetings}
onChange={e => form.setData('toolbox_meetings', e.target.value)} />
</div>
<div>
<Label htmlFor="safety_observations">Safety Observations</Label>
<Input id="safety_observations" type="number" min="0" placeholder="e.g. 7"
value={form.data.safety_observations}
onChange={e => form.setData('safety_observations', e.target.value)} />
</div>
</div>
</div>
<Separator />
{/* Reactive */}
<div>
<h4 className="text-sm font-medium text-gray-700 mb-1">Reactive Safety Incidents</h4>
<p className="text-xs text-gray-400 mb-3">All fields should ideally remain at zero.</p>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
{[
{ id: 'fatalities', label: 'Fatalities' },
{ id: 'major_injuries', label: 'Major Injuries' },
{ id: 'first_aid_cases', label: 'First Aid Cases' },
{ id: 'medical_cases', label: 'Medical Cases' },
{ id: 'near_misses', label: 'Near Misses' },
{ id: 'environmental_damage', label: 'Environmental' },
{ id: 'property_damage', label: 'Property Damage' },
{ id: 'fines', label: 'Fines / Costs (₱)' },
].map(field => (
<div key={field.id}>
<Label htmlFor={field.id}>{field.label}</Label>
<Input
id={field.id}
type="number"
min="0"
step={field.id === 'fines' ? '0.01' : '1'}
value={(form.data as Record<string, string>)[field.id]}
onChange={e => form.setData(field.id as keyof typeof form.data, e.target.value)}
/>
</div>
))}
</div>
</div>
</CardContent>
</Card>
{/* Narrative */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<FileText className="h-5 w-5 text-blue-600" />
Weekly Narrative
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label htmlFor="narrative_status">Operations Status</Label>
<textarea
id="narrative_status"
rows={3}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-gray-500 focus:ring-1 focus:ring-gray-500"
placeholder="e.g. Normal operations. All activities on track..."
value={form.data.narrative_status}
onChange={e => form.setData('narrative_status', e.target.value)}
/>
{fieldError('narrative_status')}
</div>
<div>
<Label htmlFor="narrative_weather">Weather Impact</Label>
<textarea
id="narrative_weather"
rows={2}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-gray-500 focus:ring-1 focus:ring-gray-500"
placeholder="e.g. Intermittent rainfall due to tropical weather..."
value={form.data.narrative_weather}
onChange={e => form.setData('narrative_weather', e.target.value)}
/>
{fieldError('narrative_weather')}
</div>
<div>
<Label htmlFor="narrative_compliance">Compliance Notes</Label>
<textarea
id="narrative_compliance"
rows={2}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-gray-500 focus:ring-1 focus:ring-gray-500"
placeholder="e.g. Toolbox meetings conducted consistently for worker alignment..."
value={form.data.narrative_compliance}
onChange={e => form.setData('narrative_compliance', e.target.value)}
/>
{fieldError('narrative_compliance')}
</div>
</CardContent>
</Card>
{/* Actions */}
<div className="flex justify-end gap-3">
<Link href={route('projects.reports.index', project.ulid)}>
<Button type="button" variant="outline">Cancel</Button>
</Link>
<Button type="submit" disabled={form.processing}>
<Save className="mr-2 h-4 w-4" />
Save as Draft
</Button>
</div>
</form>
</div>
</div>
</AuthenticatedLayout>
);
}