mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-24 07:28:19 +02:00
feat: add mobile customer orders page with order creation and status update functionality
This commit is contained in:
1 parent
676d8cee96
commit
4a25c2040b
3 files changed
+165
-50
No files matched your search
@@ -58,6 +58,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
|
||||||
// Schema for form validation
|
// Schema for form validation
|
||||||
const customerOrderFormSchema = z.object({
|
const customerOrderFormSchema = z.object({
|
||||||
@@ -177,8 +178,8 @@ export default function MobileCustomerOrdersPage() {
|
|||||||
productDesignation: data.productName,
|
productDesignation: data.productName,
|
||||||
status: "En attente de Commande",
|
status: "En attente de Commande",
|
||||||
groupId,
|
groupId,
|
||||||
isPromotionalPrice: false,
|
isPromotionalPrice: data.isPromotionalPrice,
|
||||||
customerNotified: false
|
customerNotified: data.customerNotified
|
||||||
};
|
};
|
||||||
|
|
||||||
createMutation.mutate(submitData);
|
createMutation.mutate(submitData);
|
||||||
@@ -422,6 +423,46 @@ export default function MobileCustomerOrdersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="isPromotionalPrice"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel>
|
||||||
|
Prix promotionnel / Pub
|
||||||
|
</FormLabel>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="customerNotified"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel>
|
||||||
|
Client déjà notifié
|
||||||
|
</FormLabel>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="notes"
|
name="notes"
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
|
||||||
const dlcFormSchema = z.object({
|
const dlcFormSchema = z.object({
|
||||||
@@ -65,6 +66,7 @@ const dlcFormSchema = z.object({
|
|||||||
dateType: z.enum(["dlc", "ddm", "dluo"]).default("dlc"),
|
dateType: z.enum(["dlc", "ddm", "dluo"]).default("dlc"),
|
||||||
supplierId: z.coerce.number().min(1, "Fournisseur requis"),
|
supplierId: z.coerce.number().min(1, "Fournisseur requis"),
|
||||||
status: z.string().default("en_cours"),
|
status: z.string().default("en_cours"),
|
||||||
|
notes: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function MobileDlcPage() {
|
export default function MobileDlcPage() {
|
||||||
@@ -132,7 +134,8 @@ export default function MobileDlcPage() {
|
|||||||
dlcDate: format(new Date(), 'yyyy-MM-dd'),
|
dlcDate: format(new Date(), 'yyyy-MM-dd'),
|
||||||
dateType: "dlc",
|
dateType: "dlc",
|
||||||
supplierId: 1,
|
supplierId: 1,
|
||||||
status: "en_cours"
|
status: "en_cours",
|
||||||
|
notes: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -389,6 +392,19 @@ export default function MobileDlcPage() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="notes"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Notes internes</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="Remarques..." className="h-20" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button type="submit" className="w-full bg-orange-600 hover:bg-orange-700 h-12 text-lg font-bold shadow-lg mt-4" disabled={createMutation.isPending}>
|
<Button type="submit" className="w-full bg-orange-600 hover:bg-orange-700 h-12 text-lg font-bold shadow-lg mt-4" disabled={createMutation.isPending}>
|
||||||
{createMutation.isPending ? "Ajout..." : "Ajouter le produit"}
|
{createMutation.isPending ? "Ajout..." : "Ajouter le produit"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -61,11 +61,15 @@ import {
|
|||||||
|
|
||||||
// Schema
|
// Schema
|
||||||
const savFormSchema = z.object({
|
const savFormSchema = z.object({
|
||||||
customerName: z.string().min(1, "Nom client requis"),
|
clientName: z.string().optional(),
|
||||||
customerPhone: z.string().optional(),
|
clientPhone: z.string().optional(),
|
||||||
productName: z.string().min(1, "Produit requis"),
|
productGencode: z.string().min(1, "Gencode requis"),
|
||||||
issueDescription: z.string().min(1, "Description requise"),
|
productReference: z.string().optional(),
|
||||||
priority: z.enum(["low", "medium", "high", "critical"]).default("medium"),
|
productDesignation: z.string().min(1, "Désignation requise"),
|
||||||
|
supplierId: z.coerce.number().min(1, "Fournisseur requis"),
|
||||||
|
problemType: z.enum(["defectueux", "pieces_manquantes", "non_conforme", "autre"]).default("defectueux"),
|
||||||
|
problemDescription: z.string().min(1, "Description requise"),
|
||||||
|
priority: z.enum(["faible", "normale", "haute", "critique"]).default("normale"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function MobileSavPage() {
|
export default function MobileSavPage() {
|
||||||
@@ -88,6 +92,11 @@ export default function MobileSavPage() {
|
|||||||
enabled: !!selectedStoreId && !!user,
|
enabled: !!selectedStoreId && !!user,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: suppliers = [] } = useQuery({
|
||||||
|
queryKey: ['/api/suppliers'],
|
||||||
|
queryFn: () => apiRequest('/api/suppliers')
|
||||||
|
});
|
||||||
|
|
||||||
const { data: groups = [] } = useQuery({
|
const { data: groups = [] } = useQuery({
|
||||||
queryKey: ['/api/groups'],
|
queryKey: ['/api/groups'],
|
||||||
queryFn: () => apiRequest('/api/groups')
|
queryFn: () => apiRequest('/api/groups')
|
||||||
@@ -118,11 +127,15 @@ export default function MobileSavPage() {
|
|||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(savFormSchema),
|
resolver: zodResolver(savFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
customerName: "",
|
clientName: "",
|
||||||
customerPhone: "",
|
clientPhone: "",
|
||||||
productName: "",
|
productGencode: "",
|
||||||
issueDescription: "",
|
productReference: "",
|
||||||
priority: "medium"
|
productDesignation: "",
|
||||||
|
supplierId: 0,
|
||||||
|
problemType: "defectueux",
|
||||||
|
problemDescription: "",
|
||||||
|
priority: "normale"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -139,23 +152,23 @@ export default function MobileSavPage() {
|
|||||||
createMutation.mutate({
|
createMutation.mutate({
|
||||||
...data,
|
...data,
|
||||||
groupId,
|
groupId,
|
||||||
status: "open"
|
status: "nouveau"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredTickets = tickets.filter((t: any) =>
|
const filteredTickets = tickets.filter((t: any) =>
|
||||||
t.customerName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
t.clientName?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
t.productName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
t.productDesignation?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
t.ticketNumber?.toLowerCase().includes(searchTerm.toLowerCase())
|
t.ticketNumber?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
const getStatusBadge = (status: string) => {
|
const getStatusBadge = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "open": return <Badge className="bg-blue-100 text-blue-800 border-blue-200">Ouvert</Badge>;
|
case "nouveau": return <Badge className="bg-blue-100 text-blue-800 border-blue-200">Nouveau</Badge>;
|
||||||
case "in_progress": return <Badge className="bg-yellow-100 text-yellow-800 border-yellow-200">En cours</Badge>;
|
case "en_cours": return <Badge className="bg-yellow-100 text-yellow-800 border-yellow-200">En cours</Badge>;
|
||||||
case "waiting_parts": return <Badge className="bg-orange-100 text-orange-800 border-orange-200">Pièces</Badge>;
|
case "attente_pieces": return <Badge className="bg-orange-100 text-orange-800 border-orange-200">Pièces</Badge>;
|
||||||
case "resolved": return <Badge className="bg-green-100 text-green-800 border-green-200">Résolu</Badge>;
|
case "resolu": return <Badge className="bg-green-100 text-green-800 border-green-200">Résolu</Badge>;
|
||||||
case "closed": return <Badge className="bg-gray-100 text-gray-800 border-gray-200">Fermé</Badge>;
|
case "ferme": return <Badge className="bg-gray-100 text-gray-800 border-gray-200">Fermé</Badge>;
|
||||||
default: return <Badge variant="outline">{status}</Badge>;
|
default: return <Badge variant="outline">{status}</Badge>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -203,8 +216,8 @@ export default function MobileSavPage() {
|
|||||||
#{ticket.ticketNumber} • {format(new Date(ticket.createdAt), "dd/MM", { locale: fr })}
|
#{ticket.ticketNumber} • {format(new Date(ticket.createdAt), "dd/MM", { locale: fr })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-gray-900">{ticket.customerName}</h3>
|
<h3 className="font-bold text-gray-900">{ticket.clientName || 'Client inconnu'}</h3>
|
||||||
<p className="text-sm text-gray-600 font-medium">{ticket.productName}</p>
|
<p className="text-sm text-gray-600 font-medium">{ticket.productDesignation}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -231,12 +244,12 @@ export default function MobileSavPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-2 rounded text-xs text-gray-700 line-clamp-2 mb-3">
|
<div className="bg-gray-50 p-2 rounded text-xs text-gray-700 line-clamp-2 mb-3">
|
||||||
{ticket.issueDescription}
|
{ticket.problemDescription}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ticket.customerPhone && (
|
{ticket.clientPhone && (
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
<a href={`tel:${ticket.customerPhone}`} className="flex items-center gap-2 text-sm text-blue-600 font-medium bg-blue-50 px-3 py-1.5 rounded-full">
|
<a href={`tel:${ticket.clientPhone}`} className="flex items-center gap-2 text-sm text-blue-600 font-medium bg-blue-50 px-3 py-1.5 rounded-full">
|
||||||
<Phone className="h-3.5 w-3.5" />
|
<Phone className="h-3.5 w-3.5" />
|
||||||
Appeler
|
Appeler
|
||||||
</a>
|
</a>
|
||||||
@@ -262,41 +275,66 @@ export default function MobileSavPage() {
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 pb-8">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 pb-8">
|
||||||
<div className="bg-gray-50 p-3 rounded-lg space-y-3">
|
<div className="bg-gray-50 p-3 rounded-lg space-y-3">
|
||||||
<h3 className="font-medium text-sm text-gray-500 uppercase">Client</h3>
|
<h3 className="font-medium text-sm text-gray-500 uppercase">Produit</h3>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="customerName"
|
name="productGencode"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Nom</FormLabel>
|
<FormLabel>Gencode (EAN13)</FormLabel>
|
||||||
<FormControl><Input {...field} /></FormControl>
|
<FormControl><Input placeholder="Scanner..." {...field} /></FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="customerPhone"
|
name="productDesignation"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Téléphone</FormLabel>
|
<FormLabel>Désignation</FormLabel>
|
||||||
<FormControl><Input type="tel" {...field} /></FormControl>
|
<FormControl><Input placeholder="Nom du produit" {...field} /></FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="supplierId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Fournisseur</FormLabel>
|
||||||
|
<Select onValueChange={(val) => field.onChange(parseInt(val))} defaultValue={field.value?.toString()}>
|
||||||
|
<FormControl><SelectTrigger><SelectValue placeholder="Choisir..." /></SelectTrigger></FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{suppliers.map((s: any) => (
|
||||||
|
<SelectItem key={s.id} value={s.id.toString()}>{s.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="problemType"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Type de problème</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
|
<FormControl><SelectTrigger><SelectValue /></SelectTrigger></FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="defectueux">Défectueux</SelectItem>
|
||||||
|
<SelectItem value="pieces_manquantes">Pièces manquantes</SelectItem>
|
||||||
|
<SelectItem value="non_conforme">Non conforme</SelectItem>
|
||||||
|
<SelectItem value="autre">Autre</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-3 rounded-lg space-y-3">
|
<div className="bg-gray-50 p-3 rounded-lg space-y-3">
|
||||||
<h3 className="font-medium text-sm text-gray-500 uppercase">Problème</h3>
|
<h3 className="font-medium text-sm text-gray-500 uppercase">Description & Client</h3>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="productName"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Produit</FormLabel>
|
|
||||||
<FormControl><Input placeholder="Marque, Modèle..." {...field} /></FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="priority"
|
name="priority"
|
||||||
@@ -306,10 +344,10 @@ export default function MobileSavPage() {
|
|||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
<FormControl><SelectTrigger><SelectValue /></SelectTrigger></FormControl>
|
<FormControl><SelectTrigger><SelectValue /></SelectTrigger></FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="low">Faible</SelectItem>
|
<SelectItem value="faible">Faible</SelectItem>
|
||||||
<SelectItem value="medium">Moyenne</SelectItem>
|
<SelectItem value="normale">Normale</SelectItem>
|
||||||
<SelectItem value="high">Haute</SelectItem>
|
<SelectItem value="haute">Haute</SelectItem>
|
||||||
<SelectItem value="critical">Critique</SelectItem>
|
<SelectItem value="critique">Critique</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -317,11 +355,31 @@ export default function MobileSavPage() {
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="issueDescription"
|
name="problemDescription"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Description Panne</FormLabel>
|
<FormLabel>Description Panne</FormLabel>
|
||||||
<FormControl><Textarea {...field} className="h-24" /></FormControl>
|
<FormControl><Textarea {...field} className="h-20" /></FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="clientName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nom Client</FormLabel>
|
||||||
|
<FormControl><Input {...field} /></FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="clientPhone"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Téléphone</FormLabel>
|
||||||
|
<FormControl><Input type="tel" {...field} /></FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in new issue
Block a user