update summary,
This commit is contained in:
@@ -1,11 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
# PROMPT
|
RULES:
|
||||||
|
|
||||||
this is a subset of a typescript project
|
show loading when fetching record from db
|
||||||
|
show error when fetch record failed
|
||||||
clone `LessonTypeCount`, `LessonCategoriesCount` to `UserCount` and do modifiy to get the count of users, thanks.
|
|
||||||
*/
|
*/
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import getAllUserMetasCount from '@/db/UserMetas/GetAllCount';
|
import getAllUserMetasCount from '@/db/UserMetas/GetAllCount';
|
||||||
@@ -15,6 +14,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Summary } from '@/components/dashboard/overview/summary';
|
import { Summary } from '@/components/dashboard/overview/summary';
|
||||||
|
|
||||||
import { LoadingSummary } from '../LoadingSummary';
|
import { LoadingSummary } from '../LoadingSummary';
|
||||||
|
import { ErrorSummary } from '../ErrorSummary';
|
||||||
|
|
||||||
function ActiveUserCount(): React.JSX.Element {
|
function ActiveUserCount(): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -50,7 +50,15 @@ function ActiveUserCount(): React.JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showError) return <div>{errorDetail}</div>;
|
if (showError)
|
||||||
|
return (
|
||||||
|
<ErrorSummary
|
||||||
|
diff={10}
|
||||||
|
icon={UsersIcon}
|
||||||
|
title={t('用戶數量')}
|
||||||
|
trend="up"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Summary
|
<Summary
|
||||||
|
@@ -0,0 +1,99 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
/*
|
||||||
|
# NOTES:
|
||||||
|
Show error to user when loading error,
|
||||||
|
use together with:
|
||||||
|
|
||||||
|
- src/components/dashboard/overview/summary/ActiveUserCount/index.tsx
|
||||||
|
*/
|
||||||
|
import * as React from 'react';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Card from '@mui/material/Card';
|
||||||
|
import CardContent from '@mui/material/CardContent';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import type { Icon } from '@phosphor-icons/react/dist/lib/types';
|
||||||
|
import { TrendDown as TrendDownIcon } from '@phosphor-icons/react/dist/ssr/TrendDown';
|
||||||
|
import { TrendUp as TrendUpIcon } from '@phosphor-icons/react/dist/ssr/TrendUp';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export interface SummaryProps {
|
||||||
|
diff: number;
|
||||||
|
icon: Icon;
|
||||||
|
title: string;
|
||||||
|
trend: 'up' | 'down';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ErrorSummary({ diff, icon: Icon, title, trend }: SummaryProps): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={3}
|
||||||
|
sx={{ alignItems: 'center' }}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
'--Avatar-size': '48px',
|
||||||
|
bgcolor: 'var(--mui-palette-background-paper)',
|
||||||
|
boxShadow: 'var(--mui-shadows-8)',
|
||||||
|
color: 'var(--mui-palette-text-primary)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon fontSize="var(--icon-fontSize-lg)" />
|
||||||
|
</Avatar>
|
||||||
|
<div>
|
||||||
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
variant="body1"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h3">Error</Typography>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
<Divider />
|
||||||
|
<Box sx={{ p: '16px' }}>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
sx={{ alignItems: 'center' }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
color: trend === 'up' ? 'var(--mui-palette-success-main)' : 'var(--mui-palette-error-main)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{trend === 'up' ? (
|
||||||
|
<TrendUpIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
|
) : (
|
||||||
|
<TrendDownIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
variant="body2"
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
color={trend === 'up' ? 'success.main' : 'error.main'}
|
||||||
|
component="span"
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
|
{new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format(diff / 100)}
|
||||||
|
</Typography>{' '}
|
||||||
|
{trend === 'up' ? t('increase') : t('decrease')} {t('vs last month')}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
@@ -15,6 +15,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Summary } from '@/components/dashboard/overview/summary';
|
import { Summary } from '@/components/dashboard/overview/summary';
|
||||||
|
|
||||||
import { LoadingSummary } from '../LoadingSummary';
|
import { LoadingSummary } from '../LoadingSummary';
|
||||||
|
import { ErrorSummary } from '../ErrorSummary';
|
||||||
|
|
||||||
function LessonCategoriesCount(): React.JSX.Element {
|
function LessonCategoriesCount(): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -40,10 +41,26 @@ function LessonCategoriesCount(): React.JSX.Element {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (showLoading) {
|
if (showLoading) {
|
||||||
return <LoadingSummary diff={10} icon={ListChecksIcon} title={t('用戶數量')} trend="up" />;
|
return (
|
||||||
|
<LoadingSummary
|
||||||
|
diff={10}
|
||||||
|
icon={ListChecksIcon}
|
||||||
|
title={t('用戶數量')}
|
||||||
|
trend="up"
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showError) return <div>{errorDetail}</div>;
|
if (showError) {
|
||||||
|
return (
|
||||||
|
<ErrorSummary
|
||||||
|
icon={ListChecksIcon}
|
||||||
|
title={t('Error')}
|
||||||
|
diff={0}
|
||||||
|
trend="down"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Summary
|
<Summary
|
||||||
|
@@ -1,5 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
/*
|
||||||
|
RULES:
|
||||||
|
|
||||||
|
show loading when fetching record from db
|
||||||
|
show error when fetch record failed
|
||||||
|
*/
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import GetAllCount from '@/db/LessonTypes/GetAllCount.tsx';
|
import GetAllCount from '@/db/LessonTypes/GetAllCount.tsx';
|
||||||
import { ListChecks as ListChecksIcon } from '@phosphor-icons/react/dist/ssr/ListChecks';
|
import { ListChecks as ListChecksIcon } from '@phosphor-icons/react/dist/ssr/ListChecks';
|
||||||
@@ -7,6 +13,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { Summary } from '@/components/dashboard/overview/summary';
|
import { Summary } from '@/components/dashboard/overview/summary';
|
||||||
import { LoadingSummary } from '@/components/dashboard/overview/summary/LoadingSummary';
|
import { LoadingSummary } from '@/components/dashboard/overview/summary/LoadingSummary';
|
||||||
|
import { ErrorSummary } from '@/components/dashboard/overview/summary/ErrorSummary';
|
||||||
|
|
||||||
function LessonTypeCount(): React.JSX.Element {
|
function LessonTypeCount(): React.JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -42,11 +49,10 @@ function LessonTypeCount(): React.JSX.Element {
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<Summary
|
<ErrorSummary
|
||||||
amount={0}
|
|
||||||
diff={0}
|
diff={0}
|
||||||
icon={ListChecksIcon}
|
icon={ListChecksIcon}
|
||||||
title={t('Error')}
|
title={t('課程類型')}
|
||||||
trend="down"
|
trend="down"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@@ -1,5 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
/*
|
||||||
|
NOTES:
|
||||||
|
show loading when loading summary
|
||||||
|
use with:
|
||||||
|
- src/components/dashboard/overview/summary/ActiveUserCount/index.tsx
|
||||||
|
*/
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
@@ -26,7 +32,11 @@ export function Summary({ amount, diff, icon: Icon, title, trend }: SummaryProps
|
|||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={3}
|
||||||
|
sx={{ alignItems: 'center' }}
|
||||||
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{
|
sx={{
|
||||||
'--Avatar-size': '48px',
|
'--Avatar-size': '48px',
|
||||||
@@ -38,7 +48,10 @@ export function Summary({ amount, diff, icon: Icon, title, trend }: SummaryProps
|
|||||||
<Icon fontSize="var(--icon-fontSize-lg)" />
|
<Icon fontSize="var(--icon-fontSize-lg)" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<Typography color="text.secondary" variant="body1">
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
variant="body1"
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h3">{new Intl.NumberFormat('en-US').format(amount)}</Typography>
|
<Typography variant="h3">{new Intl.NumberFormat('en-US').format(amount)}</Typography>
|
||||||
@@ -47,7 +60,11 @@ export function Summary({ amount, diff, icon: Icon, title, trend }: SummaryProps
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box sx={{ p: '16px' }}>
|
<Box sx={{ p: '16px' }}>
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
sx={{ alignItems: 'center' }}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -62,8 +79,15 @@ export function Summary({ amount, diff, icon: Icon, title, trend }: SummaryProps
|
|||||||
<TrendDownIcon fontSize="var(--icon-fontSize-md)" />
|
<TrendDownIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Typography color="text.secondary" variant="body2">
|
<Typography
|
||||||
<Typography color={trend === 'up' ? 'success.main' : 'error.main'} component="span" variant="subtitle2">
|
color="text.secondary"
|
||||||
|
variant="body2"
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
color={trend === 'up' ? 'success.main' : 'error.main'}
|
||||||
|
component="span"
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
{new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format(diff / 100)}
|
{new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format(diff / 100)}
|
||||||
</Typography>{' '}
|
</Typography>{' '}
|
||||||
{trend === 'up' ? t('increase') : t('decrease')} {t('vs last month')}
|
{trend === 'up' ? t('increase') : t('decrease')} {t('vs last month')}
|
||||||
|
Reference in New Issue
Block a user