WFDASHDRIVER-APP/components/ProblemModal.tsx
SJCoetz b80551fe4d refactor(ui): rebrand app, fix supplier naming
- rename `supplier` field to `suppliers` in OrderItem interface and PocketBase expand queries
- complete visual rebrand to warm cream (#f2f1ed) and dark brown (#26251e) color palette with custom CSS variables
- add accessibility improvements: skip-to-main content link, focus-visible styles, reduced motion support, and proper ARIA labels
- update all component styling to match new theme, including offline indicator, install prompt, login page, modals, map, bottom nav and global CSS
- standardize status colors: use green for success, red for errors, retain orange (#f54e00) as primary accent CTAs
2026-07-05 22:32:20 +02:00

192 lines
No EOL
7.5 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-[#e6e5e0] bottom-sheet w-full max-w-md flex flex-col max-h-[calc(100dvh-1rem)] sm:max-h-[85vh] border border-[rgba(38,37,30,0.1)] animate-slide-up">
<form
onSubmit={handleSubmit}
className="flex flex-col min-h-0 flex-1"
>
{/* Drag indicator */}
<div className="sm:hidden flex justify-center pt-2 pb-1">
<div className="w-10 h-1 bg-[rgba(38,37,30,0.2)] rounded-full" />
</div>
<div className="flex items-center justify-between px-6 pt-2 pb-5">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 bg-[#fbeaef] rounded-xl flex items-center justify-center">
<AlertTriangle size={18} className="text-[#b3224a]" />
</div>
<h2 className="text-lg font-bold text-[#26251e]">Report Problem</h2>
</div>
<button
type="button"
onClick={onClose}
aria-label="Close"
className="p-2 rounded-xl hover:bg-[rgba(38,37,30,0.06)] active:bg-[rgba(38,37,30,0.12)] transition-colors"
>
<X size={18} className="text-[rgba(38,37,30,0.55)]" />
</button>
</div>
{/* Scrollable body */}
<div className="flex-1 min-h-0 overflow-y-auto custom-scrollbar px-6 pb-6 space-y-5">
{/* Item info */}
<div className="p-4 bg-[#fbeaef] border border-[#f3c4d2] rounded-xl">
<p className="text-sm font-semibold text-[#26251e] truncate">{item.description}</p>
<p className="text-xs text-[rgba(38,37,30,0.55)] mt-1">
{item.expand?.supplier?.suppliername || 'Unknown Supplier'}
</p>
</div>
{/* Problem type */}
<div>
<label className="block text-sm font-semibold text-[#26251e] 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(''); }}
aria-pressed={problemType === type.value}
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-[#cf2d56] bg-[#fbeaef] text-[#8a1b3a] font-semibold'
: 'border-[rgba(38,37,30,0.1)] bg-[#f7f7f4] text-[#26251e] active:border-[rgba(38,37,30,0.2)]'
)}
>
{type.label}
</button>
))}
</div>
</div>
{/* Description */}
<div>
<label htmlFor="problem-details" className="block text-sm font-semibold text-[#26251e] mb-2">
Details *
</label>
<textarea
id="problem-details"
value={description}
onChange={(e) => { setDescription(e.target.value); setError(''); }}
className="w-full p-3 text-sm bg-[#f2f1ed] border border-[rgba(38,37,30,0.1)] rounded-xl text-[#26251e] placeholder:text-[rgba(38,37,30,0.4)] focus:ring-2 focus:ring-[#f54e00]/30 focus:border-[#f54e00] focus:bg-[#f7f7f4] 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-[#26251e] 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-[#1f8a65] bg-[#e6f1ec] text-[#125a3e]'
: 'border-[rgba(38,37,30,0.15)] bg-[#f2f1ed] text-[rgba(38,37,30,0.55)] active:border-[rgba(38,37,30,0.3)]'
)}
>
<Camera size={20} aria-hidden="true" />
<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-[#fbeaef] border border-[#f3c4d2] rounded-xl animate-fade-in" role="alert">
<p className="text-xs text-[#8a1b3a] font-medium">{error}</p>
</div>
)}
</div>
{/* Pinned footer — sits above BottomNav (z-50). pb accounts for
the 4rem nav height + safe-area inset + visual gap so the
button never hides under the tab bar. */}
<div
className="px-6 pt-3 pb-[calc(4rem+env(safe-area-inset-bottom,0px)+0.75rem)] border-t border-[rgba(38,37,30,0.08)] bg-[#e6e5e0] flex-shrink-0"
style={{ boxShadow: '0 -8px 24px rgba(0,0,0,0.06)' }}
>
<button
type="submit"
className={cn(
'w-full py-4 px-4 rounded-xl text-white font-semibold text-[15px]',
'bg-[#cf2d56] active:bg-[#8a1b3a] transition-all',
'touch-manipulation press-feedback min-h-[56px]'
)}
style={{ boxShadow: '0 4px 12px rgba(207,45,86,0.3)' }}
>
Report Problem
</button>
</div>
</form>
</div>
</div>
);
}