WFDASHDRIVER-APP/components/ProblemModal.tsx
SJCoetz db10eb95c7 Add PWA support and UI redesign
Add full PWA support (icons, manifest, service worker, pwa lib and bridge) and introduce install/offline UI. Generate app icons via a new scripts/generate-icons.js and include multiple icon sizes in public/icons; update manifest and layout head meta/preconnect. Revamp global styles (globals.css) with new utilities, animations, spacing for bottom nav, card shadows, skeletons and accessibility tweaks. Polish major screens (login, collections, map, settings) with improved headers, loading states, touch/press feedback, and layout adjustments; add new components (InstallPrompt, OfflineIndicator, PWABridge) and update various components to match new visual/UX patterns. Update Dockerfile to generate icons and use Next.js static export output (/out) for nginx, and bump package setup to support icon generation.
2026-05-27 00:34:50 +02:00

174 lines
No EOL
6.1 KiB
TypeScript

'use client';
import { useState } from 'react';
import { OrderItem, PROBLEM_TYPES } from '@/lib/types';
import { cn } from '@/lib/utils';
import { X, Camera, AlertTriangle } from 'lucide-react';
interface ProblemModalProps {
item: OrderItem | null;
isOpen: boolean;
onClose: () => void;
onConfirm: (item: OrderItem, problemType: string, description: string, photo?: File) => void;
}
export default function ProblemModal({
item,
isOpen,
onClose,
onConfirm,
}: ProblemModalProps) {
const [problemType, setProblemType] = useState('');
const [description, setDescription] = useState('');
const [photo, setPhoto] = useState<File | null>(null);
const [error, setError] = useState('');
if (!isOpen || !item) return null;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (!problemType) {
setError('Please select a problem type');
return;
}
if (!description.trim()) {
setError('Please describe the problem');
return;
}
onConfirm(item, problemType, description, photo || undefined);
setProblemType('');
setDescription('');
setPhoto(null);
setError('');
};
return (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center">
<div className="absolute inset-0 bg-black/50 modal-overlay" onClick={onClose} />
<div className="relative bg-white bottom-sheet w-full max-w-md p-6 animate-slide-up max-h-[90vh] overflow-y-auto custom-scrollbar">
{/* Drag indicator */}
<div className="sm:hidden flex justify-center mb-4">
<div className="w-10 h-1 bg-gray-300 rounded-full" />
</div>
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 bg-red-50 rounded-xl flex items-center justify-center">
<AlertTriangle size={18} className="text-red-600" />
</div>
<h2 className="text-lg font-bold text-gray-900">Report Problem</h2>
</div>
<button
onClick={onClose}
className="p-2 rounded-xl hover:bg-gray-100 active:bg-gray-200 transition-colors"
>
<X size={18} className="text-gray-400" />
</button>
</div>
{/* Item info */}
<div className="mb-5 p-4 bg-red-50 border border-red-100 rounded-xl">
<p className="text-sm font-semibold text-gray-900 truncate">{item.description}</p>
<p className="text-xs text-gray-500 mt-1">
{item.expand?.supplier?.suppliername || 'Unknown Supplier'}
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-5">
{/* Problem type */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
What went wrong? *
</label>
<div className="space-y-2">
{PROBLEM_TYPES.map((type) => (
<button
key={type.value}
type="button"
onClick={() => { setProblemType(type.value); setError(''); }}
className={cn(
'w-full py-3 px-4 rounded-xl text-sm text-left border-2 transition-all',
'touch-manipulation press-feedback-sm',
problemType === type.value
? 'border-red-500 bg-red-50 text-red-700 font-semibold'
: 'border-gray-150 bg-white text-gray-700 active:border-gray-300'
)}
>
{type.label}
</button>
))}
</div>
</div>
{/* Description */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Details *
</label>
<textarea
value={description}
onChange={(e) => { setDescription(e.target.value); setError(''); }}
className="w-full p-3 text-sm bg-gray-50 border border-gray-200 rounded-xl focus:ring-2 focus:ring-red-500/30 focus:border-red-500 focus:bg-white transition-all resize-none selectable"
rows={3}
placeholder="Describe the problem in detail..."
autoFocus
/>
</div>
{/* Photo */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Photo (optional)
</label>
<label
htmlFor="problem-photo"
className={cn(
'flex items-center justify-center gap-2 py-3 px-4 rounded-xl border-2 border-dashed cursor-pointer',
'transition-all touch-manipulation press-feedback-sm',
photo
? 'border-green-400 bg-green-50 text-green-700'
: 'border-gray-200 bg-gray-50 text-gray-500 active:border-gray-400'
)}
>
<Camera size={20} />
<span className="text-sm font-medium">
{photo ? 'Photo attached' : 'Take or choose photo'}
</span>
<input
type="file"
accept="image/*"
capture="environment"
onChange={(e) => setPhoto(e.target.files?.[0] || null)}
className="hidden"
id="problem-photo"
/>
</label>
</div>
{/* Error */}
{error && (
<div className="p-3 bg-red-50 border border-red-100 rounded-xl animate-fade-in">
<p className="text-xs text-red-600 font-medium">{error}</p>
</div>
)}
{/* Submit */}
<button
type="submit"
className={cn(
'w-full py-4 px-4 rounded-xl text-white font-semibold text-[15px]',
'bg-red-500 active:bg-red-600 transition-all shadow-lg shadow-red-500/25',
'touch-manipulation press-feedback'
)}
>
Report Problem
</button>
</form>
</div>
</div>
);
}