115 lines
4 KiB
JavaScript
115 lines
4 KiB
JavaScript
|
|
import React, { useState } from 'react';
|
|
import { Helmet } from 'react-helmet';
|
|
import { Plus } from 'lucide-react';
|
|
import KanbanBoard from '@/components/KanbanBoard';
|
|
import PrimaryButton from '@/components/PrimaryButton';
|
|
import SmartForm from '@/components/SmartForm';
|
|
import FormModal from '@/components/FormModal';
|
|
import { mockOpportunities } from '@/data/mockData';
|
|
import { z } from 'zod';
|
|
import { toast } from '@/components/ui/use-toast';
|
|
|
|
const opportunitySchema = z.object({
|
|
name: z.string().min(3, "Le nom est requis"),
|
|
clientId: z.string().min(1, "Le client est requis"),
|
|
amount: z.string().min(1, "Le montant est requis"),
|
|
stage: z.string().min(1, "L'étape est requise"),
|
|
probability: z.string().optional(),
|
|
closeDate: z.string().min(1, "Date requise"),
|
|
description: z.string().optional()
|
|
});
|
|
|
|
const OpportunitiesPage = () => {
|
|
const [items, setItems] = useState(mockOpportunities);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
|
|
const columns = [
|
|
{ id: 'new', title: 'Nouveau' },
|
|
{ id: 'qualification', title: 'Qualification' },
|
|
{ id: 'proposal', title: 'Proposition' },
|
|
{ id: 'negotiation', title: 'Négociation' },
|
|
{ id: 'won', title: 'Gagné' },
|
|
{ id: 'lost', title: 'Perdu' }
|
|
];
|
|
|
|
const handleDragEnd = (result) => {
|
|
if (!result.destination) return;
|
|
const { source, destination, draggableId } = result;
|
|
if (source.droppableId === destination.droppableId) return;
|
|
|
|
const newItems = items.map(item =>
|
|
item.id.toString() === draggableId
|
|
? { ...item, stage: destination.droppableId }
|
|
: item
|
|
);
|
|
|
|
setItems(newItems);
|
|
toast({
|
|
title: "Étape mise à jour",
|
|
description: "L'opportunité a changé d'étape avec succès.",
|
|
});
|
|
};
|
|
|
|
const handleCreate = (data) => {
|
|
// Simulation
|
|
const newOpp = {
|
|
id: Math.random(),
|
|
...data,
|
|
amount: parseFloat(data.amount),
|
|
probability: parseInt(data.probability) || 0,
|
|
client: "Client Simulé", // In real app, lookup client name
|
|
owner: "Jean Dupont"
|
|
};
|
|
setItems([...items, newOpp]);
|
|
setIsModalOpen(false);
|
|
toast({ title: "Opportunité créée", description: `${data.name} a été ajouté au pipeline.` });
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Helmet>
|
|
<title>Pipeline - Bijou ERP</title>
|
|
</Helmet>
|
|
|
|
<div className="h-[calc(100vh-120px)] flex flex-col">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Pipeline</h1>
|
|
<PrimaryButton icon={Plus} onClick={() => setIsModalOpen(true)}>Nouvelle opportunité</PrimaryButton>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-hidden">
|
|
<KanbanBoard
|
|
columns={columns}
|
|
items={items}
|
|
onDragEnd={handleDragEnd}
|
|
onItemClick={(item) => toast({ title: "Détail", description: `Ouverture de ${item.name}` })}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<FormModal
|
|
isOpen={isModalOpen}
|
|
onClose={() => setIsModalOpen(false)}
|
|
title="Nouvelle opportunité"
|
|
>
|
|
<SmartForm
|
|
schema={opportunitySchema}
|
|
onSubmit={handleCreate}
|
|
onCancel={() => setIsModalOpen(false)}
|
|
fields={[
|
|
{ name: 'name', label: 'Nom de l\'opportunité', placeholder: 'Ex: Contrat annuel 2025' },
|
|
{ name: 'clientId', label: 'Client', type: 'select', options: [{ value: '1', label: 'ACME Corp' }, { value: '2', label: 'TechStart' }] },
|
|
{ name: 'amount', label: 'Montant (€)', type: 'number' },
|
|
{ name: 'stage', label: 'Étape', type: 'select', options: columns.map(c => ({ value: c.id, label: c.title })) },
|
|
{ name: 'probability', label: 'Probabilité (%)', type: 'number' },
|
|
{ name: 'closeDate', label: 'Date de clôture estimée', type: 'date' },
|
|
{ name: 'description', label: 'Description', type: 'textarea', fullWidth: true },
|
|
]}
|
|
/>
|
|
</FormModal>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default OpportunitiesPage;
|