mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
4 files changed
+205
-77
No files matched your search
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
FileText,
|
||||
Search,
|
||||
@@ -63,6 +64,7 @@ const avoirSchema = z.object({
|
||||
invoiceReference: z.string().optional(),
|
||||
amount: z.coerce.number().optional(),
|
||||
comment: z.string().optional(),
|
||||
commercialProcessed: z.boolean().default(false),
|
||||
status: z.enum(["En attente de demande", "Demandé", "Reçu"]).default("En attente de demande"),
|
||||
});
|
||||
|
||||
@@ -132,6 +134,7 @@ export default function MobileAvoirsPage() {
|
||||
invoiceReference: "",
|
||||
amount: 0,
|
||||
comment: "",
|
||||
commercialProcessed: false,
|
||||
status: "En attente de demande"
|
||||
}
|
||||
});
|
||||
@@ -148,8 +151,7 @@ export default function MobileAvoirsPage() {
|
||||
|
||||
createMutation.mutate({
|
||||
...data,
|
||||
groupId,
|
||||
commercialProcessed: false
|
||||
groupId
|
||||
});
|
||||
};
|
||||
|
||||
@@ -344,34 +346,45 @@ export default function MobileAvoirsPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="comment"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Commentaire / Raison</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea placeholder="Erreur prix, manquant..." {...field} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full bg-indigo-600 hover:bg-indigo-700 h-12 text-lg font-bold shadow-lg mt-4" disabled={createMutation.isPending}>
|
||||
{createMutation.isPending ? "Création..." : "Créer la demande"}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="commercialProcessed"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center space-x-3 space-y-0 p-2 bg-gray-50 rounded mt-2">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel className="font-normal text-sm text-gray-700">
|
||||
Déjà traité par commercial
|
||||
</FormLabel>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* FAB */}
|
||||
<Button
|
||||
className="fixed bottom-20 right-4 h-14 w-14 rounded-full shadow-lg bg-indigo-600 hover:bg-indigo-700 p-0 flex items-center justify-center z-50"
|
||||
onClick={() => setIsCreateOpen(true)}
|
||||
>
|
||||
<Plus className="h-6 w-6 text-white" />
|
||||
</Button>
|
||||
</MobileLayout>
|
||||
<Button type="submit" className="w-full bg-indigo-600 hover:bg-indigo-700 h-12 text-lg font-bold shadow-lg mt-4" disabled={createMutation.isPending}>
|
||||
{createMutation.isPending ? "Création..." : "Créer la demande"}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
{/* FAB */ }
|
||||
<Button
|
||||
className="fixed bottom-20 right-4 h-14 w-14 rounded-full shadow-lg bg-indigo-600 hover:bg-indigo-700 p-0 flex items-center justify-center z-50"
|
||||
onClick={() => setIsCreateOpen(true)}
|
||||
>
|
||||
<Plus className="h-6 w-6 text-white" />
|
||||
</Button>
|
||||
</MobileLayout >
|
||||
);
|
||||
}
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
// Schema for form validation
|
||||
const customerOrderFormSchema = z.object({
|
||||
@@ -177,8 +178,8 @@ export default function MobileCustomerOrdersPage() {
|
||||
productDesignation: data.productName,
|
||||
status: "En attente de Commande",
|
||||
groupId,
|
||||
isPromotionalPrice: false,
|
||||
customerNotified: false
|
||||
isPromotionalPrice: data.isPromotionalPrice,
|
||||
customerNotified: data.customerNotified
|
||||
};
|
||||
|
||||
createMutation.mutate(submitData);
|
||||
@@ -422,6 +423,46 @@ export default function MobileCustomerOrdersPage() {
|
||||
/>
|
||||
</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
|
||||
control={form.control}
|
||||
name="notes"
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
|
||||
const dlcFormSchema = z.object({
|
||||
@@ -65,6 +66,7 @@ const dlcFormSchema = z.object({
|
||||
dateType: z.enum(["dlc", "ddm", "dluo"]).default("dlc"),
|
||||
supplierId: z.coerce.number().min(1, "Fournisseur requis"),
|
||||
status: z.string().default("en_cours"),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
export default function MobileDlcPage() {
|
||||
@@ -132,7 +134,8 @@ export default function MobileDlcPage() {
|
||||
dlcDate: format(new Date(), 'yyyy-MM-dd'),
|
||||
dateType: "dlc",
|
||||
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}>
|
||||
{createMutation.isPending ? "Ajout..." : "Ajouter le produit"}
|
||||
</Button>
|
||||
|
||||
@@ -61,11 +61,15 @@ import {
|
||||
|
||||
// Schema
|
||||
const savFormSchema = z.object({
|
||||
customerName: z.string().min(1, "Nom client requis"),
|
||||
customerPhone: z.string().optional(),
|
||||
productName: z.string().min(1, "Produit requis"),
|
||||
issueDescription: z.string().min(1, "Description requise"),
|
||||
priority: z.enum(["low", "medium", "high", "critical"]).default("medium"),
|
||||
clientName: z.string().optional(),
|
||||
clientPhone: z.string().optional(),
|
||||
productGencode: z.string().min(1, "Gencode requis"),
|
||||
productReference: z.string().optional(),
|
||||
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() {
|
||||
@@ -88,6 +92,11 @@ export default function MobileSavPage() {
|
||||
enabled: !!selectedStoreId && !!user,
|
||||
});
|
||||
|
||||
const { data: suppliers = [] } = useQuery({
|
||||
queryKey: ['/api/suppliers'],
|
||||
queryFn: () => apiRequest('/api/suppliers')
|
||||
});
|
||||
|
||||
const { data: groups = [] } = useQuery({
|
||||
queryKey: ['/api/groups'],
|
||||
queryFn: () => apiRequest('/api/groups')
|
||||
@@ -118,11 +127,15 @@ export default function MobileSavPage() {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(savFormSchema),
|
||||
defaultValues: {
|
||||
customerName: "",
|
||||
customerPhone: "",
|
||||
productName: "",
|
||||
issueDescription: "",
|
||||
priority: "medium"
|
||||
clientName: "",
|
||||
clientPhone: "",
|
||||
productGencode: "",
|
||||
productReference: "",
|
||||
productDesignation: "",
|
||||
supplierId: 0,
|
||||
problemType: "defectueux",
|
||||
problemDescription: "",
|
||||
priority: "normale"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -139,23 +152,23 @@ export default function MobileSavPage() {
|
||||
createMutation.mutate({
|
||||
...data,
|
||||
groupId,
|
||||
status: "open"
|
||||
status: "nouveau"
|
||||
});
|
||||
};
|
||||
|
||||
const filteredTickets = tickets.filter((t: any) =>
|
||||
t.customerName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
t.productName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
t.clientName?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
t.productDesignation?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
t.ticketNumber?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case "open": return <Badge className="bg-blue-100 text-blue-800 border-blue-200">Ouvert</Badge>;
|
||||
case "in_progress": 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 "resolved": 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 "nouveau": return <Badge className="bg-blue-100 text-blue-800 border-blue-200">Nouveau</Badge>;
|
||||
case "en_cours": return <Badge className="bg-yellow-100 text-yellow-800 border-yellow-200">En cours</Badge>;
|
||||
case "attente_pieces": return <Badge className="bg-orange-100 text-orange-800 border-orange-200">Pièces</Badge>;
|
||||
case "resolu": return <Badge className="bg-green-100 text-green-800 border-green-200">Résolu</Badge>;
|
||||
case "ferme": return <Badge className="bg-gray-100 text-gray-800 border-gray-200">Fermé</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 })}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-gray-900">{ticket.customerName}</h3>
|
||||
<p className="text-sm text-gray-600 font-medium">{ticket.productName}</p>
|
||||
<h3 className="font-bold text-gray-900">{ticket.clientName || 'Client inconnu'}</h3>
|
||||
<p className="text-sm text-gray-600 font-medium">{ticket.productDesignation}</p>
|
||||
</div>
|
||||
|
||||
<DropdownMenu>
|
||||
@@ -231,12 +244,12 @@ export default function MobileSavPage() {
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-2 rounded text-xs text-gray-700 line-clamp-2 mb-3">
|
||||
{ticket.issueDescription}
|
||||
{ticket.problemDescription}
|
||||
</div>
|
||||
|
||||
{ticket.customerPhone && (
|
||||
{ticket.clientPhone && (
|
||||
<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" />
|
||||
Appeler
|
||||
</a>
|
||||
@@ -262,41 +275,66 @@ export default function MobileSavPage() {
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 pb-8">
|
||||
<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
|
||||
control={form.control}
|
||||
name="customerName"
|
||||
name="productGencode"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nom</FormLabel>
|
||||
<FormControl><Input {...field} /></FormControl>
|
||||
<FormLabel>Gencode (EAN13)</FormLabel>
|
||||
<FormControl><Input placeholder="Scanner..." {...field} /></FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerPhone"
|
||||
name="productDesignation"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Téléphone</FormLabel>
|
||||
<FormControl><Input type="tel" {...field} /></FormControl>
|
||||
<FormLabel>Désignation</FormLabel>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="productName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Produit</FormLabel>
|
||||
<FormControl><Input placeholder="Marque, Modèle..." {...field} /></FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<h3 className="font-medium text-sm text-gray-500 uppercase">Description & Client</h3>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="priority"
|
||||
@@ -306,10 +344,10 @@ export default function MobileSavPage() {
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl><SelectTrigger><SelectValue /></SelectTrigger></FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="low">Faible</SelectItem>
|
||||
<SelectItem value="medium">Moyenne</SelectItem>
|
||||
<SelectItem value="high">Haute</SelectItem>
|
||||
<SelectItem value="critical">Critique</SelectItem>
|
||||
<SelectItem value="faible">Faible</SelectItem>
|
||||
<SelectItem value="normale">Normale</SelectItem>
|
||||
<SelectItem value="haute">Haute</SelectItem>
|
||||
<SelectItem value="critique">Critique</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
@@ -317,11 +355,31 @@ export default function MobileSavPage() {
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="issueDescription"
|
||||
name="problemDescription"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user