Merge pull request #459 from R0m1k3/1.1

1.1
This commit is contained in:
LogiFlow authored and GitHub committed 2025-09-22 17:26:12 +02:00
commit a9f6875d48
3 files changed
+122 -67

No files matched your search

+4
View File
@@ -27,6 +27,10 @@ externalPort = 3002
localPort = 41343 localPort = 41343
externalPort = 3001 externalPort = 3001
[[ports]]
localPort = 43431
externalPort = 3003
[workflows] [workflows]
runButton = "Project" runButton = "Project"
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

+118 -67
View File
@@ -20,10 +20,10 @@ export default function Analytics() {
const { user } = useAuthUnified(); const { user } = useAuthUnified();
const { selectedStoreId } = useStore(); const { selectedStoreId } = useStore();
// États des filtres // États des filtres - élargir la période par défaut pour inclure plus de données
const [dateRange, setDateRange] = useState({ const [dateRange, setDateRange] = useState({
from: startOfMonth(new Date()), from: addDays(new Date(), -365), // Dernière année
to: endOfMonth(new Date()) to: new Date()
}); });
const [selectedSuppliers, setSelectedSuppliers] = useState<number[]>([]); const [selectedSuppliers, setSelectedSuppliers] = useState<number[]>([]);
const [selectedStores, setSelectedStores] = useState<number[]>([]); const [selectedStores, setSelectedStores] = useState<number[]>([]);
@@ -56,11 +56,17 @@ export default function Analytics() {
const { data: summary, isLoading: summaryLoading } = useQuery({ const { data: summary, isLoading: summaryLoading } = useQuery({
queryKey: ['/api/analytics/summary', queryParams], queryKey: ['/api/analytics/summary', queryParams],
queryFn: async () => { queryFn: async () => {
console.log('📊 [ANALYTICS] Fetching summary with params:', queryParams);
const response = await fetch(`/api/analytics/summary?${queryParams}`, { const response = await fetch(`/api/analytics/summary?${queryParams}`, {
credentials: 'include' credentials: 'include'
}); });
if (!response.ok) throw new Error('Failed to fetch summary'); if (!response.ok) {
return response.json(); console.error('❌ [ANALYTICS] Summary fetch failed:', response.status, response.statusText);
throw new Error('Failed to fetch summary');
}
const data = await response.json();
console.log('✅ [ANALYTICS] Summary data received:', data);
return data;
}, },
refetchInterval: isRefreshing ? 30000 : false refetchInterval: isRefreshing ? 30000 : false
}); });
@@ -68,11 +74,18 @@ export default function Analytics() {
const { data: timeseries } = useQuery({ const { data: timeseries } = useQuery({
queryKey: ['/api/analytics/timeseries', queryParams, granularity], queryKey: ['/api/analytics/timeseries', queryParams, granularity],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`/api/analytics/timeseries?${queryParams}&granularity=${granularity}`, { const url = `/api/analytics/timeseries?${queryParams}&granularity=${granularity}`;
console.log('📊 [ANALYTICS] Fetching timeseries:', url);
const response = await fetch(url, {
credentials: 'include' credentials: 'include'
}); });
if (!response.ok) throw new Error('Failed to fetch timeseries'); if (!response.ok) {
return response.json(); console.error('❌ [ANALYTICS] Timeseries fetch failed:', response.status, response.statusText);
throw new Error('Failed to fetch timeseries');
}
const data = await response.json();
console.log('✅ [ANALYTICS] Timeseries data received:', data?.length || 0, 'entries');
return data;
} }
}); });
@@ -271,7 +284,7 @@ export default function Analytics() {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="all">Tous les fournisseurs</SelectItem> <SelectItem value="all">Tous les fournisseurs</SelectItem>
{suppliers?.map((supplier: any) => ( {Array.isArray(suppliers) && suppliers.map((supplier: any) => (
<SelectItem key={supplier.id} value={supplier.id.toString()}> <SelectItem key={supplier.id} value={supplier.id.toString()}>
{selectedSuppliers.includes(supplier.id) ? "✓ " : ""}{supplier.name} {selectedSuppliers.includes(supplier.id) ? "✓ " : ""}{supplier.name}
</SelectItem> </SelectItem>
@@ -304,7 +317,7 @@ export default function Analytics() {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="all">Tous les magasins</SelectItem> <SelectItem value="all">Tous les magasins</SelectItem>
{stores?.map((store: any) => ( {Array.isArray(stores) && stores.map((store: any) => (
<SelectItem key={store.id} value={store.id.toString()}> <SelectItem key={store.id} value={store.id.toString()}>
{selectedStores.includes(store.id) ? "✓ " : ""}{store.name} {selectedStores.includes(store.id) ? "✓ " : ""}{store.name}
</SelectItem> </SelectItem>
@@ -391,17 +404,27 @@ export default function Analytics() {
<CardDescription>Commandes vs Livraisons</CardDescription> <CardDescription>Commandes vs Livraisons</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ResponsiveContainer width="100%" height={300}> {Array.isArray(timeseries) && timeseries.length > 0 ? (
<LineChart data={timeseries || []}> <ResponsiveContainer width="100%" height={300}>
<CartesianGrid strokeDasharray="3 3" /> <LineChart data={timeseries}>
<XAxis dataKey="date" /> <CartesianGrid strokeDasharray="3 3" />
<YAxis /> <XAxis dataKey="date" />
<Tooltip /> <YAxis />
<Legend /> <Tooltip />
<Line type="monotone" dataKey="orders" stroke="#8884d8" name="Commandes" /> <Legend />
<Line type="monotone" dataKey="deliveries" stroke="#82ca9d" name="Livraisons" /> <Line type="monotone" dataKey="orders" stroke="#8884d8" name="Commandes" />
</LineChart> <Line type="monotone" dataKey="deliveries" stroke="#82ca9d" name="Livraisons" />
</ResponsiveContainer> </LineChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
<div className="text-center">
<Activity className="h-12 w-12 mx-auto mb-3 opacity-30" />
<p className="font-medium">Aucune donnée temporelle</p>
<p className="text-sm mt-1">Vérifiez la période sélectionnée</p>
</div>
</div>
)}
</CardContent> </CardContent>
</Card> </Card>
@@ -415,25 +438,35 @@ export default function Analytics() {
<CardDescription>Top 5 fournisseurs</CardDescription> <CardDescription>Top 5 fournisseurs</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ResponsiveContainer width="100%" height={300}> {Array.isArray(bySupplier) && bySupplier.length > 0 ? (
<RechartsChart> <ResponsiveContainer width="100%" height={300}>
<Pie <RechartsChart>
data={bySupplier?.slice(0, 5) || []} <Pie
cx="50%" data={bySupplier.slice(0, 5)}
cy="50%" cx="50%"
labelLine={false} cy="50%"
label={(entry) => entry.supplierName} labelLine={false}
outerRadius={80} label={(entry) => entry.supplierName}
fill="#8884d8" outerRadius={80}
dataKey="deliveries" fill="#8884d8"
> dataKey="deliveries"
{bySupplier?.slice(0, 5).map((entry: any, index: number) => ( >
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> {bySupplier.slice(0, 5).map((entry: any, index: number) => (
))} <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
</Pie> ))}
<Tooltip /> </Pie>
</RechartsChart> <Tooltip />
</ResponsiveContainer> </RechartsChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
<div className="text-center">
<PieChart className="h-12 w-12 mx-auto mb-3 opacity-30" />
<p className="font-medium">Aucune donnée fournisseur</p>
<p className="text-sm mt-1">Aucune livraison trouvée</p>
</div>
</div>
)}
</CardContent> </CardContent>
</Card> </Card>
@@ -447,17 +480,27 @@ export default function Analytics() {
<CardDescription>Commandes et livraisons</CardDescription> <CardDescription>Commandes et livraisons</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ResponsiveContainer width="100%" height={300}> {Array.isArray(byStore) && byStore.length > 0 ? (
<BarChart data={byStore?.slice(0, 5) || []} layout="horizontal"> <ResponsiveContainer width="100%" height={300}>
<CartesianGrid strokeDasharray="3 3" /> <BarChart data={byStore.slice(0, 5)} layout="horizontal">
<XAxis type="number" /> <CartesianGrid strokeDasharray="3 3" />
<YAxis dataKey="storeName" type="category" /> <XAxis type="number" />
<Tooltip /> <YAxis dataKey="storeName" type="category" />
<Legend /> <Tooltip />
<Bar dataKey="orders" fill="#8884d8" name="Commandes" /> <Legend />
<Bar dataKey="deliveries" fill="#82ca9d" name="Livraisons" /> <Bar dataKey="orders" fill="#8884d8" name="Commandes" />
</BarChart> <Bar dataKey="deliveries" fill="#82ca9d" name="Livraisons" />
</ResponsiveContainer> </BarChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
<div className="text-center">
<Store className="h-12 w-12 mx-auto mb-3 opacity-30" />
<p className="font-medium">Aucune donnée magasin</p>
<p className="text-sm mt-1">Aucune activité détectée</p>
</div>
</div>
)}
</CardContent> </CardContent>
</Card> </Card>
@@ -472,25 +515,33 @@ export default function Analytics() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-4"> <div className="space-y-4">
{summary?.topSuppliers?.map((supplier: any, index: number) => ( {Array.isArray(summary?.topSuppliers) && summary.topSuppliers.length > 0 ? (
<div key={supplier.id} className="flex items-center justify-between"> summary.topSuppliers.map((supplier: any, index: number) => (
<div className="flex items-center gap-3"> <div key={supplier.id} className="flex items-center justify-between">
<Badge variant="outline" className="w-8 h-8 rounded-full p-0 flex items-center justify-center"> <div className="flex items-center gap-3">
{index + 1} <Badge variant="outline" className="w-8 h-8 rounded-full p-0 flex items-center justify-center">
</Badge> {index + 1}
<div> </Badge>
<p className="font-medium">{supplier.name}</p> <div>
<p className="text-sm text-gray-600"> <p className="font-medium">{supplier.name}</p>
{supplier.count} livraisons • {supplier.amount.toFixed(2)}€ <p className="text-sm text-gray-600">
</p> {supplier.count} livraisons • {supplier.amount.toFixed(2)}€
</p>
</div>
</div>
<div className="text-right">
<div className="text-sm font-medium">{supplier.count}</div>
<div className="text-xs text-gray-600">livraisons</div>
</div> </div>
</div> </div>
<div className="text-right"> ))
<div className="text-sm font-medium">{supplier.count}</div> ) : (
<div className="text-xs text-gray-600">livraisons</div> <div className="text-center text-gray-500 py-4">
</div> <Package className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p>Aucune donnée disponible pour la période sélectionnée</p>
<p className="text-xs mt-1">Essayez d'élargir la plage de dates</p>
</div> </div>
))} )}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>