Compare commits

..

12 Commits

Author SHA1 Message Date
OlgunR
a620e646f0 feat(overview): update envelope statistics and localization in AnalyticsWidgetSummary 2025-12-11 16:20:58 +01:00
OlgunR
88c376262a feat(langs): remove French language option from language data 2025-12-11 15:54:47 +01:00
OlgunR
369c50f32c feat(overview): update AnalyticsWidgetSummary title and icon for clarity 2025-12-11 15:52:36 +01:00
OlgunR
e25a77335b feat(nav): comment out NavUpgrade component in NavContent for future reference 2025-12-11 15:33:02 +01:00
OlgunR
85dc9a20af feat(layout): enhance mobile navigation with title and improved layout structure 2025-12-10 16:25:55 +01:00
OlgunR
2743913786 feat(dashboard): refine layout and navigation behavior with improved transition effects 2025-12-10 15:43:38 +01:00
OlgunR
58fb4f2849 feat(nav): update NavDesktop and NavMobile styles for improved consistency and visibility 2025-12-10 15:38:34 +01:00
OlgunR
a2673216c9 feat(dashboard): update NavMobile component to use onToggle for menu button and adjust layout styles for responsiveness 2025-12-10 15:23:13 +01:00
OlgunR
a01bde914a feat(overview): add file statistics fetching and display in analytics view 2025-12-10 15:00:55 +01:00
OlgunR
214afea43d feat(app): update scroll-to-top button colors for improved visibility 2025-12-10 14:26:05 +01:00
OlgunR
10e1c5a971 feat(dashboard): enhance layout styles and improve header navigation appearance 2025-12-10 14:24:59 +01:00
OlgunR
f2b6d04ceb feat(app): implement scroll-to-top button with visibility toggle on scroll 2025-12-10 13:59:33 +01:00
9 changed files with 289 additions and 59 deletions

View File

@@ -114,12 +114,7 @@ export const _langs = [
value: 'de', value: 'de',
label: 'German', label: 'German',
icon: '/assets/icons/flags/ic-flag-de.svg', icon: '/assets/icons/flags/ic-flag-de.svg',
}, }
{
value: 'fr',
label: 'French',
icon: '/assets/icons/flags/ic-flag-fr.svg',
},
]; ];
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,8 +1,9 @@
import 'src/global.css'; import 'src/global.css';
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import Fab from '@mui/material/Fab'; import Fab from '@mui/material/Fab';
import KeyboardArrowUpRoundedIcon from '@mui/icons-material/KeyboardArrowUpRounded';
import { usePathname } from 'src/routes/hooks'; import { usePathname } from 'src/routes/hooks';
@@ -17,12 +18,28 @@ type AppProps = {
export default function App({ children }: AppProps) { export default function App({ children }: AppProps) {
useScrollToTop(); useScrollToTop();
const [showScrollTop, setShowScrollTop] = useState(false);
useEffect(() => {
const handleScroll = () => {
setShowScrollTop(window.scrollY > 160);
};
handleScroll();
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const handleScrollTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const ddButton = () => ( const ddButton = () => (
<Fab <Fab
size="medium" size="medium"
aria-label="Digital Data" aria-label="Scroll to top"
href="https://digitaldata.works/" onClick={handleScrollTop}
target="_blank"
sx={{ sx={{
zIndex: 9, zIndex: 9,
right: 20, right: 20,
@@ -30,14 +47,20 @@ export default function App({ children }: AppProps) {
width: 48, width: 48,
height: 48, height: 48,
position: 'fixed', position: 'fixed',
bgcolor: 'transparent', color: 'common.white',
boxShadow: 'none' bgcolor: '#a52431',
boxShadow: (theme) => theme.shadows[6],
opacity: showScrollTop ? 1 : 0,
pointerEvents: showScrollTop ? 'auto' : 'none',
transform: showScrollTop ? 'translateY(0)' : 'translateY(12px)',
transition: 'opacity 0.24s ease, transform 0.24s ease',
'&:hover': {
bgcolor: '#8c1e2a',
boxShadow: (theme) => theme.shadows[8],
},
}} }}
> >
<img <KeyboardArrowUpRoundedIcon fontSize="medium" />
src="/assets/images/dd-button-icon.webp"
alt="Digital Data"
/>
</Fab> </Fab>
); );

View File

@@ -5,10 +5,11 @@ import type { Theme } from '@mui/material/styles';
export function dashboardLayoutVars(theme: Theme) { export function dashboardLayoutVars(theme: Theme) {
return { return {
'--layout-transition-easing': 'linear', '--layout-transition-easing': 'linear',
'--layout-transition-duration': '120ms', '--layout-transition-duration': '100ms',
'--layout-nav-vertical-width': '300px', '--layout-nav-vertical-width': '300px',
'--layout-dashboard-content-pt': theme.spacing(1), '--layout-dashboard-content-pt': theme.spacing(1),
'--layout-dashboard-content-pb': theme.spacing(8), '--layout-dashboard-content-pb': theme.spacing(8),
'--layout-dashboard-content-px': theme.spacing(5), '--layout-dashboard-content-px': theme.spacing(5),
'--layout-header-zIndex': theme.zIndex.drawer + 2,
}; };
} }

View File

@@ -6,6 +6,8 @@ import { useBoolean } from 'minimal-shared/hooks';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import useMediaQuery from '@mui/material/useMediaQuery';
import { _langs, _notifications } from 'src/_mock'; import { _langs, _notifications } from 'src/_mock';
@@ -48,8 +50,19 @@ export function DashboardLayout({
layoutQuery = 'lg', layoutQuery = 'lg',
}: DashboardLayoutProps) { }: DashboardLayoutProps) {
const theme = useTheme(); const theme = useTheme();
const isDesktop = useMediaQuery(theme.breakpoints.up(layoutQuery));
const { value: open, onFalse: onClose, onTrue: onOpen } = useBoolean(); const {
value: mobileOpen,
onFalse: onMobileClose,
onToggle: onMobileToggle,
} = useBoolean();
const {
value: desktopOpen,
onFalse: onDesktopClose,
onToggle: onDesktopToggle,
} = useBoolean(true);
const renderHeader = () => { const renderHeader = () => {
const headerSlotProps: HeaderSectionProps['slotProps'] = { const headerSlotProps: HeaderSectionProps['slotProps'] = {
@@ -58,6 +71,35 @@ export function DashboardLayout({
}, },
}; };
const inheritedHeaderSx = slotProps?.header?.sx;
const headerSx: HeaderSectionProps['sx'] = [
(t) => ({
bgcolor: '#a52431',
color: t.vars.palette.common.white,
zIndex: 'var(--layout-header-zIndex)',
'--color': t.vars.palette.common.white,
'& .MuiButtonBase-root, & .MuiIconButton-root, & .MuiTypography-root, & .MuiSvgIcon-root': {
color: t.vars.palette.common.white,
},
'& .MuiInputBase-root': {
color: t.vars.palette.common.white,
bgcolor: 'rgba(255, 255, 255, 0.12)',
'& .MuiInputBase-input::placeholder': {
color: 'rgba(255, 255, 255, 0.72)',
},
'& .MuiSvgIcon-root': {
color: t.vars.palette.common.white,
},
},
}),
...(Array.isArray(inheritedHeaderSx)
? inheritedHeaderSx
: inheritedHeaderSx
? [inheritedHeaderSx]
: []),
];
const headerSlots: HeaderSectionProps['slots'] = { const headerSlots: HeaderSectionProps['slots'] = {
topArea: ( topArea: (
<Alert severity="info" sx={{ display: 'none', borderRadius: 0 }}> <Alert severity="info" sx={{ display: 'none', borderRadius: 0 }}>
@@ -65,14 +107,23 @@ export function DashboardLayout({
</Alert> </Alert>
), ),
leftArea: ( leftArea: (
<> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25 }}>
{/** @slot Nav mobile */} {/** @slot Nav mobile */}
<MenuButton <MenuButton
onClick={onOpen} onClick={isDesktop ? onDesktopToggle : onMobileToggle}
sx={{ mr: 1, ml: -1, [theme.breakpoints.up(layoutQuery)]: { display: 'none' } }} sx={{ ml: -1 }}
/> />
<NavMobile data={navData} open={open} onClose={onClose} workspaces={_workspaces} /> <Typography variant="h6" sx={{ fontWeight: 700 }}>
</> Digital Data Hub
</Typography>
<NavMobile
data={navData}
open={mobileOpen}
onClose={onMobileClose}
workspaces={_workspaces}
layoutQuery={layoutQuery}
/>
</Box>
), ),
rightArea: ( rightArea: (
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0, sm: 0.75 } }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0, sm: 0.75 } }}>
@@ -94,11 +145,12 @@ export function DashboardLayout({
return ( return (
<HeaderSection <HeaderSection
disableElevation disableElevation
disableOffset
layoutQuery={layoutQuery} layoutQuery={layoutQuery}
{...slotProps?.header} {...slotProps?.header}
slots={{ ...headerSlots, ...slotProps?.header?.slots }} slots={{ ...headerSlots, ...slotProps?.header?.slots }}
slotProps={merge(headerSlotProps, slotProps?.header?.slotProps ?? {})} slotProps={merge(headerSlotProps, slotProps?.header?.slotProps ?? {})}
sx={slotProps?.header?.sx} sx={headerSx}
/> />
); );
}; };
@@ -117,7 +169,12 @@ export function DashboardLayout({
* @Sidebar * @Sidebar
*************************************** */ *************************************** */
sidebarSection={ sidebarSection={
<NavDesktop data={navData} layoutQuery={layoutQuery} workspaces={_workspaces} /> <NavDesktop
open={desktopOpen}
data={navData}
layoutQuery={layoutQuery}
workspaces={_workspaces}
/>
} }
/** ************************************** /** **************************************
* @Footer * @Footer
@@ -129,9 +186,9 @@ export function DashboardLayout({
cssVars={{ ...dashboardLayoutVars(theme), ...cssVars }} cssVars={{ ...dashboardLayoutVars(theme), ...cssVars }}
sx={[ sx={[
{ {
[`& .${layoutClasses.sidebarContainer}`]: { [`& .${layoutClasses.main}`]: {
[theme.breakpoints.up(layoutQuery)]: { [theme.breakpoints.up(layoutQuery)]: {
pl: 'var(--layout-nav-vertical-width)', pl: desktopOpen ? 'var(--layout-nav-vertical-width)' : 0,
transition: theme.transitions.create(['padding-left'], { transition: theme.transitions.create(['padding-left'], {
easing: 'var(--layout-transition-easing)', easing: 'var(--layout-transition-easing)',
duration: 'var(--layout-transition-duration)', duration: 'var(--layout-transition-duration)',

View File

@@ -39,7 +39,8 @@ export function NavDesktop({
slots, slots,
workspaces, workspaces,
layoutQuery, layoutQuery,
}: NavContentProps & { layoutQuery: Breakpoint }) { open = true,
}: NavContentProps & { layoutQuery: Breakpoint; open?: boolean }) {
const theme = useTheme(); const theme = useTheme();
return ( return (
@@ -47,15 +48,23 @@ export function NavDesktop({
sx={{ sx={{
pt: 2.5, pt: 2.5,
px: 2.5, px: 2.5,
top: 0, top: 'var(--layout-header-desktop-height)',
left: 0, left: 0,
height: 1, height: 'calc(100% - var(--layout-header-desktop-height))',
display: 'none', display: 'none',
position: 'fixed', position: 'fixed',
flexDirection: 'column', flexDirection: 'column',
zIndex: 'var(--layout-nav-zIndex)', zIndex: 'var(--layout-nav-zIndex)',
width: 'var(--layout-nav-vertical-width)', width: 'var(--layout-nav-vertical-width)',
borderRight: `1px solid ${varAlpha(theme.vars.palette.grey['500Channel'], 0.12)}`, bgcolor: '#ffffff',
color: '#5a1a22',
borderRight: `1px solid ${varAlpha('90 24 34', 0.08)}`,
transform: open ? 'translateX(0)' : 'translateX(-100%)',
transition: theme.transitions.create(['transform'], {
easing: 'var(--layout-transition-easing)',
duration: 'var(--layout-transition-duration)',
}),
pointerEvents: open ? 'auto' : 'none',
[theme.breakpoints.up(layoutQuery)]: { [theme.breakpoints.up(layoutQuery)]: {
display: 'flex', display: 'flex',
}, },
@@ -76,8 +85,10 @@ export function NavMobile({
slots, slots,
onClose, onClose,
workspaces, workspaces,
}: NavContentProps & { open: boolean; onClose: () => void }) { layoutQuery,
}: NavContentProps & { open: boolean; onClose: () => void; layoutQuery: Breakpoint }) {
const pathname = usePathname(); const pathname = usePathname();
const theme = useTheme();
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@@ -94,8 +105,16 @@ export function NavMobile({
[`& .${drawerClasses.paper}`]: { [`& .${drawerClasses.paper}`]: {
pt: 2.5, pt: 2.5,
px: 2.5, px: 2.5,
top: 'var(--layout-header-mobile-height)',
overflow: 'unset', overflow: 'unset',
height: 'calc(100% - var(--layout-header-mobile-height))',
width: 'var(--layout-nav-mobile-width)', width: 'var(--layout-nav-mobile-width)',
bgcolor: '#ffffff',
color: '#5a1a22',
[theme.breakpoints.up(layoutQuery)]: {
top: 'var(--layout-header-desktop-height)',
height: 'calc(100% - var(--layout-header-desktop-height))',
},
...sx, ...sx,
}, },
}} }}
@@ -112,12 +131,8 @@ export function NavContent({ data, slots, workspaces, sx }: NavContentProps) {
return ( return (
<> <>
<Logo />
{slots?.topArea} {slots?.topArea}
<WorkspacesPopover data={workspaces} sx={{ my: 2 }} />
<Scrollbar fillContent> <Scrollbar fillContent>
<Box <Box
component="nav" component="nav"
@@ -156,14 +171,14 @@ export function NavContent({ data, slots, workspaces, sx }: NavContentProps) {
borderRadius: 0.75, borderRadius: 0.75,
typography: 'body2', typography: 'body2',
fontWeight: 'fontWeightMedium', fontWeight: 'fontWeightMedium',
color: theme.vars.palette.text.secondary, color: '#5a1a22',
minHeight: 44, minHeight: 44,
...(isActived && { ...(isActived && {
fontWeight: 'fontWeightSemiBold', fontWeight: 'fontWeightSemiBold',
color: theme.vars.palette.primary.main, color: '#5a1a22',
bgcolor: varAlpha(theme.vars.palette.primary.mainChannel, 0.08), bgcolor: 'rgba(90, 24, 34, 0.08)',
'&:hover': { '&:hover': {
bgcolor: varAlpha(theme.vars.palette.primary.mainChannel, 0.16), bgcolor: 'rgba(90, 24, 34, 0.14)',
}, },
}), }),
}), }),
@@ -188,7 +203,7 @@ export function NavContent({ data, slots, workspaces, sx }: NavContentProps) {
{slots?.bottomArea} {slots?.bottomArea}
<NavUpgrade /> {/* <NavUpgrade /> */}
</> </>
); );
} }

View File

@@ -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

View File

@@ -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

View File

@@ -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,61 @@ 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],
},
];
const envelopeStats = {
fullySigned: 140,
partiallySigned: 68,
declined: 26,
};
const envelopeTotal = envelopeStats.fullySigned + envelopeStats.partiallySigned + envelopeStats.declined;
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 +84,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>
@@ -67,15 +131,29 @@ export function OverviewAnalyticsView() {
<Grid size={{ xs: 12, sm: 6, md: 3 }}> <Grid size={{ xs: 12, sm: 6, md: 3 }}>
<AnalyticsWidgetSummary <AnalyticsWidgetSummary
title="Messages" title="Envelopes versendet"
percent={3.6} percent={3.6}
total={234} total={envelopeTotal}
color="error" color="error"
icon={<img alt="Messages" src="/assets/icons/glass/ic-glass-message.svg" />} icon={<img alt="Envelopes versendet" src="/assets/icons/glass/ic-glass-message.svg" />}
chart={{ chart={{
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'], categories: ['Vollständig signiert', 'Teilsigniert', 'Abgelehnt'],
series: [56, 30, 23, 54, 47, 40, 62, 73], series: [envelopeStats.fullySigned, envelopeStats.partiallySigned, envelopeStats.declined],
}} }}
details={[
{
label: 'Vollständig signiert',
value: envelopeStats.fullySigned,
},
{
label: 'Teilsigniert',
value: envelopeStats.partiallySigned,
},
{
label: 'Abgelehnt',
value: envelopeStats.declined,
},
]}
/> />
</Grid> </Grid>
@@ -95,14 +173,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>

View 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 };
}