ca-grow-ops-manager/frontend/src/components/TaskTemplateModal.tsx
fullsizemalt 32fd739ccf
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
Test / backend-test (push) Failing after 0s
Test / frontend-test (push) Failing after 0s
feat: Complete Phases 8-13 implementation
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)
2025-12-11 00:26:25 -08:00

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>
);
}