Phase 8: Visitor Management - Visitor/VisitorLog/AccessZone models - Check-in/out with badge generation - Zone occupancy tracking - Kiosk and management pages Phase 9: Messaging & Communication - Announcements with priority levels - Acknowledgement tracking - Shift notes for team handoffs - AnnouncementBanner component Phase 10: Compliance & Audit Trail - Immutable AuditLog model - Document versioning and approval workflow - Acknowledgement tracking for SOPs - CSV export for audit logs Phase 11: Accessibility & i18n - WCAG 2.1 AA compliance utilities - react-i18next with EN/ES translations - User preferences context (theme, font size, etc) - High contrast and reduced motion support Phase 12: Hardware Integration - QR code generation for batches/plants/visitors - Printable label system - Visitor badge printing Phase 13: Advanced Features - Environmental monitoring (sensors, readings, alerts) - Financial tracking (transactions, P&L reports) - AI/ML insights (yield predictions, anomaly detection)
138 lines
7 KiB
TypeScript
138 lines
7 KiB
TypeScript
import { useState } from 'react';
|
|
import { X, Save, FileText } from 'lucide-react';
|
|
import { taskTemplatesApi, CreateTaskTemplateData, TaskTemplate } from '../lib/taskTemplatesApi';
|
|
import { RoomType } from '../lib/roomsApi';
|
|
|
|
interface TaskTemplateModalProps {
|
|
template?: TaskTemplate | null;
|
|
onClose: () => void;
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
const ROOM_TYPES: RoomType[] = ['VEG', 'FLOWER', 'DRY', 'CURE', 'MOTHER', 'CLONE', 'FACILITY'];
|
|
|
|
export default function TaskTemplateModal({ template, onClose, onSuccess }: TaskTemplateModalProps) {
|
|
const [formData, setFormData] = useState<CreateTaskTemplateData>({
|
|
title: template?.title || '',
|
|
description: template?.description || '',
|
|
roomType: template?.roomType || 'VEG',
|
|
estimatedMinutes: template?.estimatedMinutes || 30,
|
|
materials: template?.materials || [],
|
|
recurrence: template?.recurrence || { type: 'manual' } // Default to manual
|
|
});
|
|
|
|
const [materialsInput, setMaterialsInput] = useState(template?.materials?.join(', ') || '');
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setIsSubmitting(true);
|
|
|
|
// Parse materials
|
|
const materials = materialsInput.split(',').map(s => s.trim()).filter(Boolean);
|
|
|
|
try {
|
|
const payload = { ...formData, materials };
|
|
|
|
if (template) {
|
|
await taskTemplatesApi.update(template.id, payload);
|
|
} else {
|
|
await taskTemplatesApi.create(payload);
|
|
}
|
|
onSuccess();
|
|
onClose();
|
|
} catch (error) {
|
|
console.error('Failed to save template', error);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in">
|
|
<div className="bg-white dark:bg-slate-800 w-full max-w-lg rounded-2xl shadow-2xl overflow-hidden animate-scale-in flex flex-col max-h-[90vh]">
|
|
<div className="p-6 border-b border-slate-200 dark:border-slate-700 flex justify-between items-center">
|
|
<div className="flex items-center gap-2">
|
|
<FileText className="text-emerald-600 dark:text-emerald-400" size={24} />
|
|
<h2 className="text-xl font-bold dark:text-white">
|
|
{template ? 'Edit Template' : 'New Task Template'}
|
|
</h2>
|
|
</div>
|
|
<button onClick={onClose} className="text-slate-500 hover:text-slate-700 dark:hover:text-slate-300">
|
|
<X size={24} />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="p-6 space-y-6 overflow-y-auto flex-1">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Title</label>
|
|
<input
|
|
type="text"
|
|
value={formData.title}
|
|
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
|
className="w-full p-3 rounded-lg bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 dark:text-white"
|
|
placeholder="e.g. Daily Veg Watering"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Room Type</label>
|
|
<select
|
|
value={formData.roomType}
|
|
onChange={(e) => setFormData({ ...formData, roomType: e.target.value })}
|
|
className="w-full p-3 rounded-lg bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 dark:text-white"
|
|
>
|
|
{ROOM_TYPES.map(type => (
|
|
<option key={type} value={type}>{type}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Est. Minutes</label>
|
|
<input
|
|
type="number"
|
|
value={formData.estimatedMinutes}
|
|
onChange={(e) => setFormData({ ...formData, estimatedMinutes: parseInt(e.target.value) || 0 })}
|
|
className="w-full p-3 rounded-lg bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 dark:text-white"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Description / Instructions</label>
|
|
<textarea
|
|
value={formData.description}
|
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
className="w-full p-3 rounded-lg bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 dark:text-white h-32 resize-none"
|
|
placeholder="Detailed SOP instructions..."
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Materials (comma separated)</label>
|
|
<input
|
|
type="text"
|
|
value={materialsInput}
|
|
onChange={(e) => setMaterialsInput(e.target.value)}
|
|
className="w-full p-3 rounded-lg bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 dark:text-white"
|
|
placeholder="e.g. 5gal buckets, bamboo stakes, twist ties"
|
|
/>
|
|
</div>
|
|
</form>
|
|
|
|
<div className="p-6 border-t border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50">
|
|
<button
|
|
onClick={handleSubmit}
|
|
disabled={isSubmitting}
|
|
className="w-full py-4 bg-emerald-600 hover:bg-emerald-700 text-white font-bold rounded-xl shadow-lg flex items-center justify-center gap-2 disabled:opacity-50 transition-all"
|
|
>
|
|
<Save size={20} />
|
|
{isSubmitting ? 'Saving...' : 'Save Template'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|