fieles neues
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts';
|
||||
import { databases, DATABASE_ID } from '@/lib/appwrite';
|
||||
import { Query } from 'appwrite';
|
||||
import Header from './Header';
|
||||
@@ -8,6 +10,13 @@ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
||||
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
} from '@/components/ui/chart';
|
||||
|
||||
function getToday() {
|
||||
const d = new Date();
|
||||
@@ -42,7 +51,30 @@ function countInRange(assets, start, end) {
|
||||
}).length;
|
||||
}
|
||||
|
||||
function countErledigtInRange(assets, start, end) {
|
||||
return assets.filter((a) => {
|
||||
if (a.status !== 'entsorgt') return false;
|
||||
const d = new Date(a.$updatedAt || a.$createdAt);
|
||||
return d >= start && d <= end;
|
||||
}).length;
|
||||
}
|
||||
|
||||
function countUeberfaelligAt(assets, endOfPeriod) {
|
||||
const cutoff = new Date(endOfPeriod);
|
||||
cutoff.setDate(cutoff.getDate() - 7);
|
||||
return assets.filter((a) => {
|
||||
const created = new Date(a.$createdAt);
|
||||
if (created > cutoff) return false;
|
||||
if (a.status === 'entsorgt') {
|
||||
const disposed = new Date(a.$updatedAt || a.$createdAt);
|
||||
return disposed > endOfPeriod;
|
||||
}
|
||||
return true;
|
||||
}).length;
|
||||
}
|
||||
|
||||
export default function FilialleiterDashboard() {
|
||||
const navigate = useNavigate();
|
||||
const { userMeta } = useAuth();
|
||||
const { showToast } = useToast();
|
||||
const locationId = userMeta?.locationId || '';
|
||||
@@ -55,15 +87,21 @@ export default function FilialleiterDashboard() {
|
||||
const loadData = useCallback(async () => {
|
||||
if (!locationId) return;
|
||||
try {
|
||||
const [assetsRes, metaRes, locsRes] = await Promise.all([
|
||||
const [assetsRes, lagerRes, metaRes, locsRes] = await Promise.all([
|
||||
databases.listDocuments(DATABASE_ID, 'assets', [Query.limit(500)]),
|
||||
databases.listDocuments(DATABASE_ID, 'lagerstandorte', [
|
||||
Query.equal('locationId', [locationId]),
|
||||
Query.limit(200),
|
||||
]),
|
||||
databases.listDocuments(DATABASE_ID, 'users_meta', [
|
||||
Query.equal('locationId', [locationId]),
|
||||
Query.limit(100),
|
||||
]),
|
||||
databases.listDocuments(DATABASE_ID, 'locations', [Query.limit(100)]),
|
||||
]);
|
||||
setOwnAssets(assetsRes.documents);
|
||||
const lagerIds = new Set(lagerRes.documents.map((l) => l.$id));
|
||||
const assetsForLocation = assetsRes.documents.filter((a) => a.lagerstandortId && lagerIds.has(a.lagerstandortId));
|
||||
setOwnAssets(assetsForLocation);
|
||||
setAllAssetsTotal(assetsRes.total);
|
||||
setAllLocationsCount(Math.max(locsRes.total, 1));
|
||||
setColleagues(metaRes.documents.filter((d) => d.userName));
|
||||
@@ -86,6 +124,57 @@ export default function FilialleiterDashboard() {
|
||||
const thisMonthCount = countInRange(ownAssets, monthStart, now);
|
||||
const lastMonthCount = countInRange(ownAssets, lastMonthStart, lastMonthEnd);
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
const days = [];
|
||||
const dayNames = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - i);
|
||||
const dayStart = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
||||
const dayEnd = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59);
|
||||
days.push({
|
||||
day: `${dayNames[d.getDay()]} ${d.getDate()}.`,
|
||||
erfasst: countInRange(ownAssets, dayStart, dayEnd),
|
||||
erledigt: countErledigtInRange(ownAssets, dayStart, dayEnd),
|
||||
ueberfaellig: countUeberfaelligAt(ownAssets, dayEnd),
|
||||
});
|
||||
}
|
||||
return days;
|
||||
}, [ownAssets]);
|
||||
|
||||
const monthChartData = useMemo(() => {
|
||||
const months = [];
|
||||
const monthNames = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
|
||||
for (let i = 5; i >= 0; i--) {
|
||||
const d = new Date();
|
||||
d.setMonth(d.getMonth() - i);
|
||||
const monthStart = new Date(d.getFullYear(), d.getMonth(), 1);
|
||||
const monthEnd = new Date(d.getFullYear(), d.getMonth() + 1, 0, 23, 59, 59);
|
||||
months.push({
|
||||
month: `${monthNames[d.getMonth()]} ${d.getFullYear().toString().slice(2)}`,
|
||||
erfasst: countInRange(ownAssets, monthStart, monthEnd),
|
||||
erledigt: countErledigtInRange(ownAssets, monthStart, monthEnd),
|
||||
ueberfaellig: countUeberfaelligAt(ownAssets, monthEnd),
|
||||
});
|
||||
}
|
||||
return months;
|
||||
}, [ownAssets]);
|
||||
|
||||
const chartConfig = {
|
||||
erfasst: {
|
||||
label: 'Erfasst',
|
||||
color: '#60a5fa',
|
||||
},
|
||||
erledigt: {
|
||||
label: 'Erledigt',
|
||||
color: '#22c55e',
|
||||
},
|
||||
ueberfaellig: {
|
||||
label: 'Überfällig',
|
||||
color: '#ef4444',
|
||||
},
|
||||
};
|
||||
|
||||
const avgAllFilialen = allLocationsCount > 0 ? Math.round(allAssetsTotal / allLocationsCount) : 0;
|
||||
const ownTotal = ownAssets.length;
|
||||
|
||||
@@ -96,6 +185,7 @@ export default function FilialleiterDashboard() {
|
||||
const open = assigned.filter((a) => a.status === 'offen').length;
|
||||
const inProgress = assigned.filter((a) => a.status === 'in_bearbeitung').length;
|
||||
return {
|
||||
userId: c.userId,
|
||||
name: c.userName,
|
||||
total: assigned.length,
|
||||
resolved,
|
||||
@@ -127,22 +217,72 @@ export default function FilialleiterDashboard() {
|
||||
</div>
|
||||
|
||||
<div className="mb-8 grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="pt-2">
|
||||
<div className="text-3xl font-bold">{todayCount}</div>
|
||||
<p className="text-sm text-muted-foreground">Heute erfasst</p>
|
||||
<p className={`mt-1 text-xs font-medium ${dayTrend.cls}`}>
|
||||
{dayTrend.arrow} Gestern: {yesterdayCount}
|
||||
</p>
|
||||
<Card className="lg:col-span-2">
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<div>
|
||||
<div className="text-3xl font-bold">{todayCount}</div>
|
||||
<p className="text-sm text-muted-foreground">Heute erfasst</p>
|
||||
<p className={`mt-1 text-xs font-medium ${dayTrend.cls}`}>
|
||||
{dayTrend.arrow} Gestern: {yesterdayCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 h-[140px] w-full">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full aspect-auto">
|
||||
<BarChart
|
||||
data={chartData}
|
||||
margin={{ left: -20, right: 12, top: 4, bottom: 0 }}
|
||||
>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
axisLine={false}
|
||||
dataKey="day"
|
||||
tickLine={false}
|
||||
tickMargin={6}
|
||||
/>
|
||||
<YAxis axisLine={false} tickCount={4} tickLine={false} tickMargin={6} tickFormatter={(v) => Math.floor(v)} />
|
||||
<ChartTooltip content={<ChartTooltipContent indicator="dashed" />} cursor={false} />
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
<Bar dataKey="erfasst" fill="var(--color-erfasst)" radius={4} />
|
||||
<Bar dataKey="erledigt" fill="var(--color-erledigt)" radius={4} />
|
||||
<Bar dataKey="ueberfaellig" fill="var(--color-ueberfaellig)" radius={4} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-2">
|
||||
<div className="text-3xl font-bold">{thisMonthCount}</div>
|
||||
<p className="text-sm text-muted-foreground">Diesen Monat</p>
|
||||
<p className={`mt-1 text-xs font-medium ${monthTrend.cls}`}>
|
||||
{monthTrend.arrow} Letzter Monat: {lastMonthCount}
|
||||
</p>
|
||||
<Card className="lg:col-span-2">
|
||||
<CardContent className="pt-4">
|
||||
<div>
|
||||
<div className="text-3xl font-bold">{thisMonthCount}</div>
|
||||
<p className="text-sm text-muted-foreground">Diesen Monat</p>
|
||||
<p className={`mt-1 text-xs font-medium ${monthTrend.cls}`}>
|
||||
{monthTrend.arrow} Letzter Monat: {lastMonthCount}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 h-[140px] w-full">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full aspect-auto">
|
||||
<BarChart
|
||||
data={monthChartData}
|
||||
margin={{ left: -20, right: 12, top: 4, bottom: 0 }}
|
||||
>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
axisLine={false}
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={6}
|
||||
/>
|
||||
<YAxis axisLine={false} tickCount={4} tickLine={false} tickMargin={6} tickFormatter={(v) => Math.floor(v)} />
|
||||
<ChartTooltip content={<ChartTooltipContent indicator="dashed" />} cursor={false} />
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
<Bar dataKey="erfasst" fill="var(--color-erfasst)" radius={4} />
|
||||
<Bar dataKey="erledigt" fill="var(--color-erledigt)" radius={4} />
|
||||
<Bar dataKey="ueberfaellig" fill="var(--color-ueberfaellig)" radius={4} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
@@ -200,7 +340,11 @@ export default function FilialleiterDashboard() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{employeeStats.map((e) => (
|
||||
<TableRow key={e.name}>
|
||||
<TableRow
|
||||
key={e.userId || e.name}
|
||||
className="cursor-pointer hover:bg-muted/50"
|
||||
onClick={() => e.userId && navigate(`/filialleiter/mitarbeiter/${e.userId}`)}
|
||||
>
|
||||
<TableCell className="font-medium">{e.name}</TableCell>
|
||||
<TableCell className="text-right tabular-nums">{e.total}</TableCell>
|
||||
<TableCell className="text-right tabular-nums">{e.open}</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user