- Added package.json with dependencies for Next.js, React, and Tailwind CSS. - Created postcss.config.js for Tailwind CSS and autoprefixer integration. - Added SVG icon for the application in public/icons/icon.svg. - Created manifest.json for PWA support with app details and icon. - Configured Tailwind CSS in tailwind.config.js with custom colors and animations. - Set up TypeScript configuration in tsconfig.json for strict type checking and module resolution.
170 lines
5.6 KiB
TypeScript
170 lines
5.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Collection, PROBLEM_TYPES } from '@/lib/types';
|
|
import { cn } from '@/lib/utils';
|
|
import { X, Camera } from 'lucide-react';
|
|
|
|
interface ProblemModalProps {
|
|
collection: Collection | null;
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onConfirm: (collection: Collection, problemType: string, description: string, photo?: File) => void;
|
|
}
|
|
|
|
export default function ProblemModal({
|
|
collection,
|
|
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 || !collection) 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(collection, 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 backdrop-blur-sm" onClick={onClose} />
|
|
<div className="relative bg-white rounded-t-2xl sm:rounded-2xl w-full max-w-md p-6 animate-slide-up">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-bold">Report Problem</h2>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-1 rounded-full hover:bg-gray-100 active:bg-gray-200 transition-colors"
|
|
>
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="mb-4 p-3 bg-red-50 rounded-lg">
|
|
<p className="text-sm font-medium truncate">
|
|
{collection.is_manual
|
|
? collection.description
|
|
: collection.expand?.order_item?.description || 'Unknown item'}
|
|
</p>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
{collection.expand?.supplier?.suppliername || 'Unknown Supplier'}
|
|
</p>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Problem Type *
|
|
</label>
|
|
<div className="grid grid-cols-1 gap-2">
|
|
{PROBLEM_TYPES.map((type) => (
|
|
<button
|
|
key={type.value}
|
|
type="button"
|
|
onClick={() => {
|
|
setProblemType(type.value);
|
|
setError('');
|
|
}}
|
|
className={cn(
|
|
'py-2.5 px-3 rounded-lg text-sm text-left border-2 transition-all',
|
|
'touch-manipulation',
|
|
problemType === type.value
|
|
? 'border-red-500 bg-red-50 text-red-700 font-medium'
|
|
: 'border-gray-200 hover:border-gray-300'
|
|
)}
|
|
>
|
|
{type.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Description *
|
|
</label>
|
|
<textarea
|
|
value={description}
|
|
onChange={(e) => {
|
|
setDescription(e.target.value);
|
|
setError('');
|
|
}}
|
|
className="w-full p-3 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent resize-none"
|
|
rows={3}
|
|
placeholder="Describe the problem in detail..."
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Photo (optional)
|
|
</label>
|
|
<div className="relative">
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
capture="environment"
|
|
onChange={(e) => setPhoto(e.target.files?.[0] || null)}
|
|
className="hidden"
|
|
id="problem-photo"
|
|
/>
|
|
<label
|
|
htmlFor="problem-photo"
|
|
className={cn(
|
|
'flex items-center justify-center gap-2 py-3 px-4 rounded-lg border-2 border-dashed cursor-pointer',
|
|
'transition-colors touch-manipulation',
|
|
photo
|
|
? 'border-green-400 bg-green-50 text-green-700'
|
|
: 'border-gray-300 hover:border-gray-400'
|
|
)}
|
|
>
|
|
<Camera size={20} />
|
|
<span className="text-sm">
|
|
{photo ? photo.name : 'Take or choose photo'}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mb-4 p-2 bg-red-50 border border-red-200 rounded-lg">
|
|
<p className="text-xs text-red-600">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
className={cn(
|
|
'w-full py-3.5 px-4 rounded-xl text-white font-semibold text-base',
|
|
'bg-red-500 active:bg-red-600 transition-colors',
|
|
'touch-manipulation flex items-center justify-center gap-2'
|
|
)}
|
|
>
|
|
Report Problem
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|