mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
adding new dashboard view. Muchas wow
This commit is contained in:
23
ui/src/components/cards/ChartCard.less
Normal file
23
ui/src/components/cards/ChartCard.less
Normal file
@@ -0,0 +1,23 @@
|
||||
.chartCard {
|
||||
/* Use provided background with slight transparency and a brighter mix */
|
||||
background: color-mix(in oklab, var(--card-bg, rgb(70 72 78)) 20%, white 80%);
|
||||
border-radius: .6rem;
|
||||
border: 1px solid color-mix(in oklab, var(--card-bg, rgb(70 72 78)) 35%, white 65%);
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.08);
|
||||
/* Ensure base text has strong contrast */
|
||||
color: var(--semi-color-text-0);
|
||||
|
||||
/* Semi Card header/title styling */
|
||||
.semi-card-header .semi-card-header-title {
|
||||
/* Derive a tinted title color with stronger contrast towards black */
|
||||
color: color-mix(in oklab, var(--card-bg, rgb(70 72 78)) 60%, black 40%);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__no__data {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 14rem;
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
92
ui/src/components/cards/DashboardCard.less
Normal file
92
ui/src/components/cards/DashboardCard.less
Normal file
@@ -0,0 +1,92 @@
|
||||
@import "DashboardCardColors.less";
|
||||
|
||||
.color-variant(@bg, @border, @text) {
|
||||
background-color: @bg;
|
||||
border: 1px solid @border;
|
||||
color: @text;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
box-sizing: border-box;
|
||||
padding: .8rem;
|
||||
border-radius: .5rem;
|
||||
border-width: 1px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.08);
|
||||
/* Make all KPI boxes the same size regardless of content/font */
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
height: 10rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.blue {
|
||||
.color-variant(@color-blue-bg, @color-blue-border, @color-blue-text);
|
||||
}
|
||||
|
||||
&.orange {
|
||||
.color-variant(@color-orange-bg, @color-orange-border, @color-orange-text);
|
||||
}
|
||||
|
||||
&.green {
|
||||
.color-variant(@color-green-bg, @color-green-border, @color-green-text);
|
||||
}
|
||||
|
||||
&.purple {
|
||||
.color-variant(@color-purple-bg, @color-purple-border, @color-purple-text);
|
||||
}
|
||||
|
||||
&.gray {
|
||||
.color-variant(@color-gray-bg, @color-gray-border, @color-gray-text);
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
/* Keep header from growing content height */
|
||||
min-height: 2rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
border-radius: .6rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__content {
|
||||
margin-top: .4rem;
|
||||
font-size: .7rem;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__value {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.1;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
opacity: .8;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
}
|
||||
19
ui/src/components/cards/DashboardCardColors.less
Normal file
19
ui/src/components/cards/DashboardCardColors.less
Normal file
@@ -0,0 +1,19 @@
|
||||
@color-blue-bg: rgba(0, 123, 255, 0.24);
|
||||
@color-blue-border: #1E40AFFF;
|
||||
@color-blue-text: #60a5fa;
|
||||
|
||||
@color-orange-bg: rgba(250, 91, 5, 0.12);
|
||||
@color-orange-border: #d33601;
|
||||
@color-orange-text: #FB923CFF;
|
||||
|
||||
@color-green-bg: rgba(38, 250, 5, 0.12);
|
||||
@color-green-border: #00c316;
|
||||
@color-green-text: #33f308;
|
||||
|
||||
@color-purple-bg: rgba(91, 3, 218, 0.38);
|
||||
@color-purple-border: #7500c3;
|
||||
@color-purple-text: #b15fff;
|
||||
|
||||
@color-gray-bg: rgba(110, 110, 110, 0.38);
|
||||
@color-gray-border: #807f7f;
|
||||
@color-gray-text: #bab9b9;
|
||||
40
ui/src/components/cards/KpiCard.jsx
Normal file
40
ui/src/components/cards/KpiCard.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import './DashboardCard.less';
|
||||
|
||||
export default function KpiCard({
|
||||
title,
|
||||
icon,
|
||||
value,
|
||||
valueFontSize = '1.5rem',
|
||||
description,
|
||||
color = 'gray',
|
||||
children,
|
||||
}) {
|
||||
return (
|
||||
<div className={`dashboard-card ${color}`}>
|
||||
<div className="dashboard-card__header">
|
||||
<div className="dashboard-card__icon">{icon}</div>
|
||||
<div className="dashboard-card__title">
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dashboard-card__content">
|
||||
<p className="dashboard-card__value" style={{ fontSize: valueFontSize }}>
|
||||
{value}
|
||||
{children}
|
||||
</p>
|
||||
{description && <span className="dashboard-card__desc">{description}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
97
ui/src/components/cards/PieChartCard.jsx
Normal file
97
ui/src/components/cards/PieChartCard.jsx
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Pie } from 'react-chartjs-2';
|
||||
import { Chart as ChartJS, ArcElement, Tooltip, Legend, Title as ChartTitle } from 'chart.js';
|
||||
|
||||
import './ChartCard.less';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend, ChartTitle);
|
||||
|
||||
export default function PieChartCard({ data = [] }) {
|
||||
const { labels, values } = React.useMemo(() => {
|
||||
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
||||
const lbls = Array.isArray(data.labels) ? data.labels : [];
|
||||
const vals = Array.isArray(data.values)
|
||||
? data.values.map((v) => (Number.isFinite(Number(v)) ? Number(v) : 0))
|
||||
: [];
|
||||
return { labels: lbls, values: vals };
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
const lbls = data.map((d) => d?.type ?? 'Unknown');
|
||||
const vals = data.map((d) => {
|
||||
const v = Number(d?.value);
|
||||
return Number.isFinite(v) ? v : 0;
|
||||
});
|
||||
return { labels: lbls, values: vals };
|
||||
}
|
||||
return { labels: [], values: [] };
|
||||
}, [data]);
|
||||
|
||||
const palette = React.useMemo(
|
||||
() => [
|
||||
'#4e79a7',
|
||||
'#f28e2b',
|
||||
'#e15759',
|
||||
'#76b7b2',
|
||||
'#59a14f',
|
||||
'#edc948',
|
||||
'#b07aa1',
|
||||
'#ff9da7',
|
||||
'#9c755f',
|
||||
'#bab0ab',
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const chartData = React.useMemo(
|
||||
() => ({
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
data: values,
|
||||
backgroundColor: labels.map((_, i) => palette[i % palette.length]),
|
||||
borderColor: labels.map((_, i) => palette[i % palette.length]),
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
[labels, values, palette],
|
||||
);
|
||||
|
||||
const options = React.useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
labels: {
|
||||
color: () => '#fff',
|
||||
},
|
||||
},
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (ctx) => {
|
||||
const label = ctx.label || '';
|
||||
const val = ctx.parsed !== undefined ? ctx.parsed : ctx.raw;
|
||||
return `${label}: ${val}%`;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const isEmpty = !labels || labels.length === 0 || !values || values.length === 0;
|
||||
|
||||
return (
|
||||
<>{isEmpty ? <div className="chartCard__no__data">No Data</div> : <Pie data={chartData} options={options} />}</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user