Compare commits
12 Commits
cb4c61d1e4
...
feat/front
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a620e646f0 | ||
|
|
88c376262a | ||
|
|
369c50f32c | ||
|
|
e25a77335b | ||
|
|
85dc9a20af | ||
|
|
2743913786 | ||
|
|
58fb4f2849 | ||
|
|
a2673216c9 | ||
|
|
a01bde914a | ||
|
|
214afea43d | ||
|
|
10e1c5a971 | ||
|
|
f2b6d04ceb |
@@ -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',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)',
|
||||||
|
|||||||
@@ -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 /> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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>
|
||||||
|
|||||||
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