feat(overview): add file statistics fetching and display in analytics view
This commit is contained in:
@@ -22,6 +22,8 @@ type Props = CardProps & {
|
|||||||
percent: number;
|
percent: number;
|
||||||
color?: PaletteColorKey;
|
color?: PaletteColorKey;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
|
details?: { label: string; value: number }[];
|
||||||
|
loading?: boolean;
|
||||||
chart: {
|
chart: {
|
||||||
series: number[];
|
series: number[];
|
||||||
categories: string[];
|
categories: string[];
|
||||||
@@ -37,6 +39,8 @@ export function AnalyticsWidgetSummary({
|
|||||||
chart,
|
chart,
|
||||||
percent,
|
percent,
|
||||||
color = 'primary',
|
color = 'primary',
|
||||||
|
details,
|
||||||
|
loading = false,
|
||||||
...other
|
...other
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -114,6 +118,23 @@ export function AnalyticsWidgetSummary({
|
|||||||
<Box sx={{ mb: 1, typography: 'subtitle2' }}>{title}</Box>
|
<Box sx={{ mb: 1, typography: 'subtitle2' }}>{title}</Box>
|
||||||
|
|
||||||
<Box sx={{ typography: 'h4' }}>{fShortenNumber(total)}</Box>
|
<Box sx={{ typography: 'h4' }}>{fShortenNumber(total)}</Box>
|
||||||
|
|
||||||
|
{details?.length ? (
|
||||||
|
<Box component="ul" sx={{ mt: 1.5, mb: 0, pl: 0, listStyle: 'none', gap: 0.5, display: 'grid' }}>
|
||||||
|
{details.map((item) => (
|
||||||
|
<Box
|
||||||
|
component="li"
|
||||||
|
key={item.label}
|
||||||
|
sx={{ display: 'flex', justifyContent: 'space-between', typography: 'caption', color: 'text.secondary' }}
|
||||||
|
>
|
||||||
|
<Box component="span">{item.label}</Box>
|
||||||
|
<Box component="span" sx={{ color: 'text.primary', fontWeight: 'fontWeightSemiBold' }}>
|
||||||
|
{loading ? '…' : fShortenNumber(item.value)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Chart
|
<Chart
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type Props = CardProps & {
|
|||||||
percent: number;
|
percent: number;
|
||||||
color?: PaletteColorKey;
|
color?: PaletteColorKey;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
|
details?: { label: string; value: number }[];
|
||||||
|
loading?: boolean;
|
||||||
chart: {
|
chart: {
|
||||||
series: number[];
|
series: number[];
|
||||||
categories: string[];
|
categories: string[];
|
||||||
@@ -37,6 +39,8 @@ export function AnalyticsWidgetSummary({
|
|||||||
chart,
|
chart,
|
||||||
percent,
|
percent,
|
||||||
color = 'primary',
|
color = 'primary',
|
||||||
|
details,
|
||||||
|
loading = false,
|
||||||
...other
|
...other
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -114,6 +118,23 @@ export function AnalyticsWidgetSummary({
|
|||||||
<Box sx={{ mb: 1, typography: 'subtitle2' }}>{title}</Box>
|
<Box sx={{ mb: 1, typography: 'subtitle2' }}>{title}</Box>
|
||||||
|
|
||||||
<Box sx={{ typography: 'h4' }}>{fShortenNumber(total)}</Box>
|
<Box sx={{ typography: 'h4' }}>{fShortenNumber(total)}</Box>
|
||||||
|
|
||||||
|
{details?.length ? (
|
||||||
|
<Box component="ul" sx={{ mt: 1.5, mb: 0, pl: 0, listStyle: 'none', gap: 0.5, display: 'grid' }}>
|
||||||
|
{details.map((item) => (
|
||||||
|
<Box
|
||||||
|
component="li"
|
||||||
|
key={item.label}
|
||||||
|
sx={{ display: 'flex', justifyContent: 'space-between', typography: 'caption', color: 'text.secondary' }}
|
||||||
|
>
|
||||||
|
<Box component="span">{item.label}</Box>
|
||||||
|
<Box component="span" sx={{ color: 'text.primary', fontWeight: 'fontWeightSemiBold' }}>
|
||||||
|
{loading ? '…' : fShortenNumber(item.value)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Chart
|
<Chart
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { useMemo, useState, useEffect } from 'react';
|
||||||
|
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import { DashboardContent } from 'src/layouts/dashboard';
|
import { DashboardContent } from 'src/layouts/dashboard';
|
||||||
import { _posts, _tasks, _traffic, _timeline } from 'src/_mock';
|
import { _posts, _tasks, _traffic, _timeline } from 'src/_mock';
|
||||||
|
import { fetchFileStatsToday } from 'src/services/file-stats-service';
|
||||||
|
|
||||||
import { AnalyticsNews } from '../analytics-news';
|
import { AnalyticsNews } from '../analytics-news';
|
||||||
import { AnalyticsTasks } from '../analytics-tasks';
|
import { AnalyticsTasks } from '../analytics-tasks';
|
||||||
@@ -17,6 +20,53 @@ import { AnalyticsConversionRates } from '../analytics-conversion-rates';
|
|||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
export function OverviewAnalyticsView() {
|
export function OverviewAnalyticsView() {
|
||||||
|
const [fileStats, setFileStats] = useState({ filed: 0, edited: 0 });
|
||||||
|
const [loadingFiles, setLoadingFiles] = useState(true);
|
||||||
|
const visitCategories = useMemo(() => {
|
||||||
|
const days = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
|
const labels = Array.from({ length: 7 }, (_, idx) => {
|
||||||
|
const d = new Date(today);
|
||||||
|
d.setDate(today.getDate() - (6 - idx));
|
||||||
|
const dayIdx = d.getDay();
|
||||||
|
return days[dayIdx];
|
||||||
|
});
|
||||||
|
|
||||||
|
labels[labels.length - 1] = 'Heute';
|
||||||
|
return labels;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const baselineFiled = [43, 33, 22, 37, 67, 68];
|
||||||
|
const baselineEdited = [51, 70, 47, 67, 40, 37];
|
||||||
|
|
||||||
|
const visitSeries = [
|
||||||
|
{
|
||||||
|
name: 'Dateien abgelegt',
|
||||||
|
data: [...baselineFiled, fileStats.filed],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dateien bearbeitet',
|
||||||
|
data: [...baselineEdited, fileStats.edited],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
fetchFileStatsToday()
|
||||||
|
.then((data) => {
|
||||||
|
if (active) setFileStats(data);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (active) setLoadingFiles(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardContent maxWidth="xl">
|
<DashboardContent maxWidth="xl">
|
||||||
<Typography variant="h4" sx={{ mb: { xs: 3, md: 5 } }}>
|
<Typography variant="h4" sx={{ mb: { xs: 3, md: 5 } }}>
|
||||||
@@ -26,13 +76,19 @@ export function OverviewAnalyticsView() {
|
|||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||||
<AnalyticsWidgetSummary
|
<AnalyticsWidgetSummary
|
||||||
title="Weekly sales"
|
title="Dateien - Heute"
|
||||||
percent={2.6}
|
percent={0}
|
||||||
total={714000}
|
total={fileStats.filed + fileStats.edited}
|
||||||
icon={<img alt="Weekly sales" src="/assets/icons/glass/ic-glass-bag.svg" />}
|
icon={<img alt="Dateien" src="/assets/icons/glass/ic-glass-bag.svg" />}
|
||||||
|
details={[
|
||||||
|
{ label: 'Dateien abgelegt', value: fileStats.filed },
|
||||||
|
{ label: 'Dateien bearbeitet', value: fileStats.edited },
|
||||||
|
]}
|
||||||
|
loading={loadingFiles}
|
||||||
chart={{
|
chart={{
|
||||||
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'],
|
categories: ['Heute'],
|
||||||
series: [22, 8, 35, 50, 82, 84, 77, 12],
|
series: [fileStats.filed, fileStats.edited],
|
||||||
|
options: { stroke: { width: 2 } },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -95,14 +151,14 @@ export function OverviewAnalyticsView() {
|
|||||||
|
|
||||||
<Grid size={{ xs: 12, md: 6, lg: 8 }}>
|
<Grid size={{ xs: 12, md: 6, lg: 8 }}>
|
||||||
<AnalyticsWebsiteVisits
|
<AnalyticsWebsiteVisits
|
||||||
title="Website visits"
|
title="Dateien verarbeitet"
|
||||||
subheader="(+43%) than last year"
|
subheader="(+43%) als letzte Woche"
|
||||||
chart={{
|
chart={{
|
||||||
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep'],
|
categories: visitCategories,
|
||||||
series: [
|
series: visitSeries,
|
||||||
{ name: 'Team A', data: [43, 33, 22, 37, 67, 68, 37, 24, 55] },
|
options: {
|
||||||
{ name: 'Team B', data: [51, 70, 47, 67, 40, 37, 24, 70, 24] },
|
tooltip: { y: { formatter: (value: number) => `${value} Dateien` } },
|
||||||
],
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
19
src/client/dd-hub-react/src/services/file-stats-service.ts
Normal file
19
src/client/dd-hub-react/src/services/file-stats-service.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export type FileStatsToday = {
|
||||||
|
filed: number;
|
||||||
|
edited: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function fetchFilesAbgelegtToday(): Promise<number> {
|
||||||
|
// Mock placeholder: returns number of files placed today
|
||||||
|
return Promise.resolve(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchFilesBearbeitetToday(): Promise<number> {
|
||||||
|
// Mock placeholder: returns number of files edited today
|
||||||
|
return Promise.resolve(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchFileStatsToday(): Promise<FileStatsToday> {
|
||||||
|
const [filed, edited] = await Promise.all([fetchFilesAbgelegtToday(), fetchFilesBearbeitetToday()]);
|
||||||
|
return { filed, edited };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user