Files
fredy/ui/src/views/dashboard/Dashboard.jsx

157 lines
5.4 KiB
React
Raw Normal View History

2025-12-14 12:23:59 +01:00
/*
* Copyright (c) 2026 by Christian Kellner.
2025-12-14 12:23:59 +01:00
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import React from 'react';
import { Button, Col, Row, Toast } from '@douyinfe/semi-ui-19';
2025-12-14 12:23:59 +01:00
import {
IconTerminal,
IconStar,
IconClock,
IconDoubleChevronLeft,
IconDoubleChevronRight,
IconStarStroked,
IconNoteMoney,
IconSearch,
IconPlayCircle,
} from '@douyinfe/semi-icons';
import { useSelector, useActions } from '../../services/state/store';
import KpiCard from '../../components/cards/KpiCard.jsx';
import PieChartCard from '../../components/cards/PieChartCard.jsx';
import Headline from '../../components/headline/Headline.jsx';
2025-12-14 12:23:59 +01:00
import './Dashboard.less';
import { xhrPost } from '../../services/xhr.js';
import { format } from '../../services/time/timeService.js';
2026-06-04 10:35:42 +02:00
import { useTranslation, useLocale } from '../../services/i18n/i18n.jsx';
2025-12-14 12:23:59 +01:00
export default function Dashboard() {
2026-06-04 10:35:42 +02:00
const t = useTranslation();
const locale = useLocale();
2025-12-14 12:23:59 +01:00
const actions = useActions();
const dashboard = useSelector((state) => state.dashboard.data);
React.useEffect(() => {
actions.dashboard.getDashboard();
}, []);
const kpis = dashboard?.kpis || { totalJobs: 0, totalListings: 0, providersUsed: 0 };
const pieData = dashboard?.pie || [];
return (
<div className="dashboard">
2026-06-04 10:35:42 +02:00
<Headline text={t('dashboard.title')} />
2026-06-04 10:35:42 +02:00
<div className="dashboard__section-label">{t('dashboard.sectionGeneral')}</div>
<Row gutter={[16, 16]} className="dashboard__row">
<Col xs={24} sm={12} md={12} lg={6} xl={6}>
<KpiCard
2026-06-04 10:35:42 +02:00
title={t('dashboard.searchInterval')}
value={`${dashboard?.general?.interval} min`}
icon={<IconClock />}
2026-06-04 10:35:42 +02:00
description={t('dashboard.searchIntervalDesc')}
/>
2025-12-14 12:23:59 +01:00
</Col>
<Col xs={24} sm={12} md={12} lg={6} xl={6}>
<KpiCard
2026-06-04 10:35:42 +02:00
title={t('dashboard.lastSearch')}
value={
dashboard?.general?.lastRun == null || dashboard?.general?.lastRun === 0
? '---'
2026-06-04 10:35:42 +02:00
: format(dashboard?.general?.lastRun, true, locale)
}
icon={<IconDoubleChevronLeft />}
2026-06-04 10:35:42 +02:00
description={t('dashboard.lastSearchDesc')}
/>
</Col>
<Col xs={24} sm={12} md={12} lg={6} xl={6}>
<KpiCard
2026-06-04 10:35:42 +02:00
title={t('dashboard.nextSearch')}
value={
dashboard?.general?.nextRun == null || dashboard?.general?.nextRun === 0
? '---'
2026-06-04 10:35:42 +02:00
: format(dashboard?.general?.nextRun, true, locale)
}
icon={<IconDoubleChevronRight />}
2026-06-04 10:35:42 +02:00
description={t('dashboard.nextSearchDesc')}
/>
</Col>
<Col xs={24} sm={12} md={12} lg={6} xl={6}>
2026-06-04 10:35:42 +02:00
<KpiCard title={t('dashboard.searchNow')} icon={<IconSearch />} description={t('dashboard.searchNowDesc')}>
<Button
size="small"
style={{ marginTop: '.2rem' }}
icon={<IconPlayCircle />}
2026-06-04 10:35:42 +02:00
aria-label={t('common.startNow')}
onClick={async () => {
try {
await xhrPost('/api/jobs/startAll', null);
2026-06-04 10:35:42 +02:00
Toast.success(t('dashboard.searchNowStarted'));
} catch {
2026-06-04 10:35:42 +02:00
Toast.error(t('dashboard.searchNowFailed'));
}
}}
>
2026-06-04 10:35:42 +02:00
{t('dashboard.searchNowButton')}
</Button>
</KpiCard>
</Col>
</Row>
2026-06-04 10:35:42 +02:00
<div className="dashboard__section-label">{t('dashboard.sectionOverview')}</div>
<Row gutter={[16, 16]} className="dashboard__row">
<Col xs={24} sm={12} md={12} lg={6} xl={6}>
<KpiCard
2026-06-04 10:35:42 +02:00
title={t('dashboard.kpiJobs')}
color="blue"
value={!kpis.totalJobs ? '---' : kpis.totalJobs}
icon={<IconTerminal />}
2026-06-04 10:35:42 +02:00
description={t('dashboard.kpiJobsDesc')}
/>
</Col>
<Col xs={24} sm={12} md={12} lg={6} xl={6}>
<KpiCard
2026-06-04 10:35:42 +02:00
title={t('dashboard.kpiListings')}
color="orange"
value={!kpis.totalListings ? '---' : kpis.totalListings}
icon={<IconStarStroked />}
2026-06-04 10:35:42 +02:00
description={t('dashboard.kpiListingsDesc')}
/>
</Col>
<Col xs={24} sm={12} md={12} lg={6} xl={6}>
<KpiCard
2026-06-04 10:35:42 +02:00
title={t('dashboard.kpiActiveListings')}
color="green"
value={!kpis.numberOfActiveListings ? '---' : kpis.numberOfActiveListings}
icon={<IconStar />}
2026-06-04 10:35:42 +02:00
description={t('dashboard.kpiActiveListingsDesc')}
/>
</Col>
<Col xs={24} sm={12} md={12} lg={6} xl={6}>
<KpiCard
2026-06-04 10:35:42 +02:00
title={t('dashboard.kpiMedianPrice')}
color="purple"
value={`${
!kpis.medianPriceOfListings
? '---'
2026-06-04 10:35:42 +02:00
: new Intl.NumberFormat(locale, { style: 'currency', currency: 'EUR' }).format(
kpis.medianPriceOfListings,
)
}`}
icon={<IconNoteMoney />}
2026-06-04 10:35:42 +02:00
description={t('dashboard.kpiMedianPriceDesc')}
/>
2025-12-14 12:23:59 +01:00
</Col>
</Row>
2026-06-04 10:35:42 +02:00
<div className="dashboard__section-label">{t('dashboard.sectionProviderInsights')}</div>
<div className="dashboard__pie-wrapper">
<PieChartCard data={pieData} />
</div>
2025-12-14 12:23:59 +01:00
</div>
);
}
Dashboard.displayName = 'Dashboard';