feat: Add DetailPage and HomePage components with KPI visualization
- Implemented DetailPage for detailed KPI analysis including charts and status badges. - Created HomePage to display an overview of KPIs categorized by security, quality, delays, costs, and maintenance. - Introduced KPI types and data structures for better type safety. - Added styles for DetailPage, HomePage, KPICard, and charts for improved UI. - Integrated web vitals reporting and setup tests for better performance tracking and testing. - Included a CSV file with mathematical formulas for KPI calculations.
This commit is contained in:
parent
aa13c49772
commit
5ecda7eef7
23
dashboard-sqdc/.gitignore
vendored
Normal file
23
dashboard-sqdc/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
46
dashboard-sqdc/README.md
Normal file
46
dashboard-sqdc/README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Getting Started with Create React App
|
||||||
|
|
||||||
|
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.\
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||||
|
|
||||||
|
The page will reload if you make edits.\
|
||||||
|
You will also see any lint errors in the console.
|
||||||
|
|
||||||
|
### `npm test`
|
||||||
|
|
||||||
|
Launches the test runner in the interactive watch mode.\
|
||||||
|
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||||
|
|
||||||
|
### `npm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `build` folder.\
|
||||||
|
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.\
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
|
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||||
|
|
||||||
|
### `npm run eject`
|
||||||
|
|
||||||
|
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||||
|
|
||||||
|
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||||
|
|
||||||
|
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||||
|
|
||||||
|
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||||
|
|
||||||
|
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||||
17628
dashboard-sqdc/package-lock.json
generated
Normal file
17628
dashboard-sqdc/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
dashboard-sqdc/package.json
Normal file
47
dashboard-sqdc/package.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "dashboard-sqdc",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/dom": "^10.4.1",
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/node": "^16.18.126",
|
||||||
|
"@types/react": "^19.2.2",
|
||||||
|
"@types/react-dom": "^19.2.2",
|
||||||
|
"chart.js": "^4.5.1",
|
||||||
|
"lucide-react": "^0.546.0",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-chartjs-2": "^5.3.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
dashboard-sqdc/public/favicon.ico
Normal file
BIN
dashboard-sqdc/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
43
dashboard-sqdc/public/index.html
Normal file
43
dashboard-sqdc/public/index.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
dashboard-sqdc/public/logo192.png
Normal file
BIN
dashboard-sqdc/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
dashboard-sqdc/public/logo512.png
Normal file
BIN
dashboard-sqdc/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
dashboard-sqdc/public/manifest.json
Normal file
25
dashboard-sqdc/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
3
dashboard-sqdc/public/robots.txt
Normal file
3
dashboard-sqdc/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
145
dashboard-sqdc/src/App.css
Normal file
145
dashboard-sqdc/src/App.css
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HEADER */
|
||||||
|
.app-header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title p {
|
||||||
|
opacity: 0.9;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-date {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NAVIGATION */
|
||||||
|
.app-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow-x: auto;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
padding: 1rem;
|
||||||
|
border: none;
|
||||||
|
background: white;
|
||||||
|
color: #333;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn.active {
|
||||||
|
background: white;
|
||||||
|
color: #667eea;
|
||||||
|
border-bottom-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CONTENT */
|
||||||
|
.app-content {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 1400px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CHARTS PAGE */
|
||||||
|
.charts-page {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-header h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-header p {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FOOTER */
|
||||||
|
.app-footer {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-top: auto;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESPONSIVE */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-content {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn {
|
||||||
|
min-width: 100px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
dashboard-sqdc/src/App.test.tsx
Normal file
9
dashboard-sqdc/src/App.test.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
test('renders learn react link', () => {
|
||||||
|
render(<App />);
|
||||||
|
const linkElement = screen.getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
125
dashboard-sqdc/src/App.tsx
Normal file
125
dashboard-sqdc/src/App.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { HomePage } from './pages/HomePage';
|
||||||
|
import { DetailPage } from './pages/DetailPage';
|
||||||
|
import { TrendChart, CategoryDistributionChart, StatusChart, CNQChart } from './components/Charts';
|
||||||
|
import { kpiData } from './data/kpiData';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
type TabType = 'home' | 'security' | 'quality' | 'delays' | 'costs' | 'maintenance' | 'charts';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [activeTab, setActiveTab] = useState<TabType>('home');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="app">
|
||||||
|
<header className="app-header">
|
||||||
|
<div className="header-content">
|
||||||
|
<div className="header-title">
|
||||||
|
<h1>📊 Dashboard SQDC</h1>
|
||||||
|
<p>Indicateurs de Performance - Sécurité, Qualité, Délais, Coûts</p>
|
||||||
|
</div>
|
||||||
|
<div className="header-date">
|
||||||
|
{new Date().toLocaleDateString('fr-FR', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav className="app-nav">
|
||||||
|
<button
|
||||||
|
className={`nav-btn ${activeTab === 'home' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('home')}
|
||||||
|
>
|
||||||
|
🏠 Accueil
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`nav-btn ${activeTab === 'security' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('security')}
|
||||||
|
>
|
||||||
|
🛡️ Sécurité
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`nav-btn ${activeTab === 'quality' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('quality')}
|
||||||
|
>
|
||||||
|
🎯 Qualité
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`nav-btn ${activeTab === 'delays' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('delays')}
|
||||||
|
>
|
||||||
|
⏱️ Délais
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`nav-btn ${activeTab === 'costs' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('costs')}
|
||||||
|
>
|
||||||
|
💰 Coûts
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`nav-btn ${activeTab === 'maintenance' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('maintenance')}
|
||||||
|
>
|
||||||
|
🔧 Maintenance
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`nav-btn ${activeTab === 'charts' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('charts')}
|
||||||
|
>
|
||||||
|
📈 Analyses
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="app-content">
|
||||||
|
{activeTab === 'home' && (
|
||||||
|
<HomePage kpiData={kpiData} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'security' && (
|
||||||
|
<DetailPage category="security" kpis={kpiData.security} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'quality' && (
|
||||||
|
<DetailPage category="quality" kpis={kpiData.quality} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'delays' && (
|
||||||
|
<DetailPage category="delays" kpis={kpiData.delays} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'costs' && (
|
||||||
|
<DetailPage category="costs" kpis={kpiData.costs} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'maintenance' && (
|
||||||
|
<DetailPage category="maintenance" kpis={kpiData.maintenance} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'charts' && (
|
||||||
|
<div className="charts-page">
|
||||||
|
<div className="charts-header">
|
||||||
|
<h1>📈 Analyses et Tendances</h1>
|
||||||
|
<p>Vue globale des tendances et des analyses comparatives</p>
|
||||||
|
</div>
|
||||||
|
<div className="charts-grid">
|
||||||
|
<TrendChart />
|
||||||
|
<CategoryDistributionChart />
|
||||||
|
<StatusChart />
|
||||||
|
<CNQChart />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer className="app-footer">
|
||||||
|
<p>Dashboard SQDC | Dernière mise à jour: {new Date().toLocaleTimeString('fr-FR')} | Données en temps réel</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
197
dashboard-sqdc/src/components/Charts.tsx
Normal file
197
dashboard-sqdc/src/components/Charts.tsx
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Line, Doughnut, Bar } from 'react-chartjs-2';
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
LineElement,
|
||||||
|
BarElement,
|
||||||
|
ArcElement,
|
||||||
|
Tooltip,
|
||||||
|
Legend
|
||||||
|
} from 'chart.js';
|
||||||
|
import { kpiData, getCategoryColor } from '../data/kpiData';
|
||||||
|
import '../styles/Charts.css';
|
||||||
|
|
||||||
|
ChartJS.register(
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
LineElement,
|
||||||
|
BarElement,
|
||||||
|
ArcElement,
|
||||||
|
Tooltip,
|
||||||
|
Legend
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TrendChart: React.FC = () => {
|
||||||
|
const data = {
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Sécurité (%)',
|
||||||
|
data: [94, 95, 96, 96],
|
||||||
|
borderColor: getCategoryColor('security'),
|
||||||
|
backgroundColor: 'rgba(231, 76, 60, 0.1)',
|
||||||
|
tension: 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Qualité (%)',
|
||||||
|
data: [75, 76, 77, 78.5],
|
||||||
|
borderColor: getCategoryColor('quality'),
|
||||||
|
backgroundColor: 'rgba(52, 152, 219, 0.1)',
|
||||||
|
tension: 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Délais (%)',
|
||||||
|
data: [97, 97.5, 97.8, 98],
|
||||||
|
borderColor: getCategoryColor('delays'),
|
||||||
|
backgroundColor: 'rgba(243, 156, 18, 0.1)',
|
||||||
|
tension: 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Coûts (%)',
|
||||||
|
data: [92, 91, 90.5, 89],
|
||||||
|
borderColor: getCategoryColor('costs'),
|
||||||
|
backgroundColor: 'rgba(39, 174, 96, 0.1)',
|
||||||
|
tension: 0.4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-container">
|
||||||
|
<h2>📈 Tendances SQDC</h2>
|
||||||
|
<Line data={data} options={{
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: { legend: { position: 'top' as const } },
|
||||||
|
scales: { y: { beginAtZero: true, max: 100 } }
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CategoryDistributionChart: React.FC = () => {
|
||||||
|
const categoryCounts = {
|
||||||
|
'Sécurité': kpiData.security.length,
|
||||||
|
'Qualité': kpiData.quality.length,
|
||||||
|
'Délais': kpiData.delays.length,
|
||||||
|
'Coûts': kpiData.costs.length,
|
||||||
|
'Maintenance': kpiData.maintenance.length
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: Object.keys(categoryCounts),
|
||||||
|
datasets: [{
|
||||||
|
data: Object.values(categoryCounts),
|
||||||
|
backgroundColor: [
|
||||||
|
getCategoryColor('security'),
|
||||||
|
getCategoryColor('quality'),
|
||||||
|
getCategoryColor('delays'),
|
||||||
|
getCategoryColor('costs'),
|
||||||
|
getCategoryColor('maintenance')
|
||||||
|
],
|
||||||
|
borderColor: 'white',
|
||||||
|
borderWidth: 2
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-container">
|
||||||
|
<h2>📊 Répartition KPI par Catégorie</h2>
|
||||||
|
<Doughnut data={data} options={{
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: { legend: { position: 'bottom' as const } }
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StatusChart: React.FC = () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const allKPIs = [
|
||||||
|
...kpiData.security,
|
||||||
|
...kpiData.quality,
|
||||||
|
...kpiData.delays,
|
||||||
|
...kpiData.costs,
|
||||||
|
...kpiData.maintenance
|
||||||
|
];
|
||||||
|
|
||||||
|
const categories = ['Sécurité', 'Qualité', 'Délais', 'Coûts', 'Maintenance'];
|
||||||
|
const categoryKPIs = [
|
||||||
|
kpiData.security,
|
||||||
|
kpiData.quality,
|
||||||
|
kpiData.delays,
|
||||||
|
kpiData.costs,
|
||||||
|
kpiData.maintenance
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: categories,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Bon',
|
||||||
|
data: categoryKPIs.map(cat => cat.filter(k => k.status === 'good').length),
|
||||||
|
backgroundColor: '#27ae60'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'À améliorer',
|
||||||
|
data: categoryKPIs.map(cat => cat.filter(k => k.status === 'warning').length),
|
||||||
|
backgroundColor: '#f39c12'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Critique',
|
||||||
|
data: categoryKPIs.map(cat => cat.filter(k => k.status === 'critical').length),
|
||||||
|
backgroundColor: '#e74c3c'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-container">
|
||||||
|
<h2>📊 État des KPI par Catégorie</h2>
|
||||||
|
<Bar data={data} options={{
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
indexAxis: 'y' as const,
|
||||||
|
plugins: { legend: { position: 'top' as const } },
|
||||||
|
scales: {
|
||||||
|
x: { stacked: true },
|
||||||
|
y: { stacked: true }
|
||||||
|
}
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CNQChart: React.FC = () => {
|
||||||
|
const data = {
|
||||||
|
labels: ['Rebuts', 'Retouches', 'Retours Clients'],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Coût (€)',
|
||||||
|
data: [8500, 7200, 2800],
|
||||||
|
backgroundColor: [
|
||||||
|
'#e74c3c',
|
||||||
|
'#f39c12',
|
||||||
|
'#3498db'
|
||||||
|
],
|
||||||
|
borderColor: 'white',
|
||||||
|
borderWidth: 2
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-container">
|
||||||
|
<h2>💔 Coûts des Non-Qualité</h2>
|
||||||
|
<Bar data={data} options={{
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: { legend: { display: false } },
|
||||||
|
scales: { y: { beginAtZero: true } }
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
67
dashboard-sqdc/src/components/KPICard.tsx
Normal file
67
dashboard-sqdc/src/components/KPICard.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { KPI } from '../types';
|
||||||
|
import '../styles/KPICard.css';
|
||||||
|
|
||||||
|
interface KPICardProps {
|
||||||
|
kpi: KPI;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KPICard: React.FC<KPICardProps> = ({ kpi, color }) => {
|
||||||
|
const getStatusIcon = () => {
|
||||||
|
switch (kpi.status) {
|
||||||
|
case 'good':
|
||||||
|
return '✓';
|
||||||
|
case 'warning':
|
||||||
|
return '⚠';
|
||||||
|
case 'critical':
|
||||||
|
return '🔴';
|
||||||
|
default:
|
||||||
|
return '•';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTrendIcon = () => {
|
||||||
|
switch (kpi.trend) {
|
||||||
|
case 'up':
|
||||||
|
return '📈';
|
||||||
|
case 'down':
|
||||||
|
return '📉';
|
||||||
|
case 'stable':
|
||||||
|
return '➡️';
|
||||||
|
default:
|
||||||
|
return '•';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusClass = () => {
|
||||||
|
return `status-${kpi.status}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="kpi-card" style={{ borderLeftColor: color }}>
|
||||||
|
<div className="kpi-header" style={{ color }}>
|
||||||
|
<h3 className="kpi-title">{kpi.name}</h3>
|
||||||
|
<span className="kpi-trend">{getTrendIcon()}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="kpi-value-section">
|
||||||
|
<div className="kpi-value">{kpi.value}</div>
|
||||||
|
<div className="kpi-unit">{kpi.unit}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="kpi-footer">
|
||||||
|
<span className={`kpi-status ${getStatusClass()}`}>
|
||||||
|
{getStatusIcon()} {kpi.status.charAt(0).toUpperCase() + kpi.status.slice(1)}
|
||||||
|
</span>
|
||||||
|
{kpi.target && (
|
||||||
|
<span className="kpi-target">
|
||||||
|
Objectif: {kpi.target} {kpi.unit}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="kpi-description">{kpi.description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
342
dashboard-sqdc/src/data/kpiData.ts
Normal file
342
dashboard-sqdc/src/data/kpiData.ts
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import { KPI, CategoryData } from '../types';
|
||||||
|
|
||||||
|
export const kpiData: CategoryData = {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
id: 'tf',
|
||||||
|
name: 'Taux de Fréquence',
|
||||||
|
value: 0.5,
|
||||||
|
unit: 'par 1M heures',
|
||||||
|
category: 'security',
|
||||||
|
target: 1.0,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Mesurer la fréquence des accidents avec arrêt.',
|
||||||
|
formula: '(Nombre d\'Accidents avec Arrêt / Nombre d\'Heures Travaillées) × 1000000',
|
||||||
|
data: [1.2, 0.9, 0.7, 0.5],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'incidents',
|
||||||
|
name: 'Incidents/Near Miss',
|
||||||
|
value: 12,
|
||||||
|
unit: 'incidents',
|
||||||
|
category: 'security',
|
||||||
|
target: 8,
|
||||||
|
status: 'warning',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Évaluer la culture de sécurité et la proactivité.',
|
||||||
|
formula: 'Compte des rapports d\'incidents (sans blessure/dommage)',
|
||||||
|
data: [8, 10, 11, 12],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'audit_compliance',
|
||||||
|
name: 'Conformité Audits',
|
||||||
|
value: 96,
|
||||||
|
unit: '%',
|
||||||
|
category: 'security',
|
||||||
|
target: 95,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Mesurer le respect des procédures de sécurité.',
|
||||||
|
formula: '(Points de Contrôle Conformes / Total Points de Contrôle) × 100',
|
||||||
|
data: [93, 94, 95, 96],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
quality: [
|
||||||
|
{
|
||||||
|
id: 'scrap_rate',
|
||||||
|
name: 'Taux de Rebut',
|
||||||
|
value: 2.1,
|
||||||
|
unit: '%',
|
||||||
|
category: 'quality',
|
||||||
|
target: 1.5,
|
||||||
|
status: 'warning',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Mesurer le pourcentage d\'unités jetées (irréparables).',
|
||||||
|
formula: '(Nombre d\'Unités Rebutées / Nombre Total d\'Unités Produites) × 100',
|
||||||
|
data: [3.2, 2.8, 2.4, 2.1],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rework_rate',
|
||||||
|
name: 'Taux de Retouche',
|
||||||
|
value: 3.8,
|
||||||
|
unit: '%',
|
||||||
|
category: 'quality',
|
||||||
|
target: 2.0,
|
||||||
|
status: 'warning',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Mesurer le pourcentage d\'unités nécessitant une reprise.',
|
||||||
|
formula: '(Nombre d\'Unités Retouchées / Nombre Total d\'Unités Produites) × 100',
|
||||||
|
data: [5.2, 4.6, 4.2, 3.8],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dpu',
|
||||||
|
name: 'Défauts par Unité (DPU)',
|
||||||
|
value: 0.45,
|
||||||
|
unit: 'défauts/unité',
|
||||||
|
category: 'quality',
|
||||||
|
target: 0.5,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Mesurer le nombre moyen de défauts par produit.',
|
||||||
|
formula: 'Nombre Total de Défauts Trouvés / Nombre Total d\'Unités Inspectées',
|
||||||
|
data: [0.68, 0.58, 0.52, 0.45],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'return_rate',
|
||||||
|
name: 'Taux de Retours Clients',
|
||||||
|
value: 1.2,
|
||||||
|
unit: '%',
|
||||||
|
category: 'quality',
|
||||||
|
target: 0.8,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Mesurer l\'impact de la non-qualité chez le client.',
|
||||||
|
formula: '(Nombre d\'Unités Retournées / Nombre Total d\'Unités Vendues) × 100',
|
||||||
|
data: [2.1, 1.8, 1.5, 1.2],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trs',
|
||||||
|
name: 'TRS (Rendement Synthétique)',
|
||||||
|
value: 78.5,
|
||||||
|
unit: '%',
|
||||||
|
category: 'quality',
|
||||||
|
target: 85,
|
||||||
|
status: 'warning',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Rendement global de la ligne de production.',
|
||||||
|
formula: 'Nb pièces bonnes × Temps cycle / Temps d\'ouverture',
|
||||||
|
data: [75.2, 76.5, 77.8, 78.5],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'oee',
|
||||||
|
name: 'OEE (Overall Equipment Effectiveness)',
|
||||||
|
value: 72.3,
|
||||||
|
unit: '%',
|
||||||
|
category: 'quality',
|
||||||
|
target: 80,
|
||||||
|
status: 'warning',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Efficacité combinée (Disponibilité, Performance, Qualité).',
|
||||||
|
formula: 'Disponibilité × Performance × Qualité',
|
||||||
|
data: [68.5, 70.2, 71.5, 72.3],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
delays: [
|
||||||
|
{
|
||||||
|
id: 'schedule_adherence',
|
||||||
|
name: 'Respect du Plan',
|
||||||
|
value: 98,
|
||||||
|
unit: '%',
|
||||||
|
category: 'delays',
|
||||||
|
target: 95,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Mesurer la capacité à atteindre le volume planifié.',
|
||||||
|
formula: '(Quantité Réellement Produite / Quantité Planifiée) × 100',
|
||||||
|
data: [96.5, 97.2, 97.6, 98.0],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cycle_time',
|
||||||
|
name: 'Temps de Cycle',
|
||||||
|
value: 45,
|
||||||
|
unit: 'min/unité',
|
||||||
|
category: 'delays',
|
||||||
|
target: 50,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Mesurer le temps nécessaire pour assembler une unité.',
|
||||||
|
formula: 'Temps Total de Production / Nombre Total d\'Unités Produites',
|
||||||
|
data: [52, 49, 47, 45],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tack_time',
|
||||||
|
name: 'Tack Time',
|
||||||
|
value: 50,
|
||||||
|
unit: 'min/unité',
|
||||||
|
category: 'delays',
|
||||||
|
target: 50,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'stable',
|
||||||
|
description: 'Temps de production requis par unité demandée.',
|
||||||
|
formula: 'Temps de production / Nombre de pièces demandées',
|
||||||
|
data: [50, 50, 50, 50],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'downtime',
|
||||||
|
name: 'Temps d\'Arrêt Imprévu',
|
||||||
|
value: 2.5,
|
||||||
|
unit: 'h/jour',
|
||||||
|
category: 'delays',
|
||||||
|
target: 1.5,
|
||||||
|
status: 'warning',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Mesurer le temps d\'arrêt non planifié de la ligne.',
|
||||||
|
formula: 'Somme des Périodes d\'Arrêt Non Planifié',
|
||||||
|
data: [1.8, 2.1, 2.3, 2.5],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
costs: [
|
||||||
|
{
|
||||||
|
id: 'cpu',
|
||||||
|
name: 'Coût par Unité',
|
||||||
|
value: 245,
|
||||||
|
unit: '€',
|
||||||
|
category: 'costs',
|
||||||
|
target: 240,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Mesurer l\'efficacité des coûts de production.',
|
||||||
|
formula: 'Coût Total de Production / Nombre Total d\'Unités Produites',
|
||||||
|
data: [258, 252, 248, 245],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'labor_productivity',
|
||||||
|
name: 'Productivité MOD',
|
||||||
|
value: 8.2,
|
||||||
|
unit: 'unités/h',
|
||||||
|
category: 'costs',
|
||||||
|
target: 8.0,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Mesurer l\'efficacité de l\'équipe d\'assemblage.',
|
||||||
|
formula: 'Nombre d\'Unités Produites / Total Heures Main-d\'œuvre Directe',
|
||||||
|
data: [7.8, 8.0, 8.1, 8.2],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cnq',
|
||||||
|
name: 'Coût des Non-Qualité',
|
||||||
|
value: 18500,
|
||||||
|
unit: '€',
|
||||||
|
category: 'costs',
|
||||||
|
target: 10000,
|
||||||
|
status: 'critical',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Mesurer le coût des défauts (retouche, rebut, retours).',
|
||||||
|
formula: 'Coût des Rebuts + Coût des Retouches + Retours clients',
|
||||||
|
data: [15200, 16800, 17800, 18500],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
maintenance: [
|
||||||
|
{
|
||||||
|
id: 'mtbf',
|
||||||
|
name: 'MTBF (Temps Moyen Entre Pannes)',
|
||||||
|
value: 450,
|
||||||
|
unit: 'heures',
|
||||||
|
category: 'maintenance',
|
||||||
|
target: 400,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Mesurer la fiabilité des équipements.',
|
||||||
|
formula: 'Temps Total de Fonctionnement / Nombre Total de Pannes',
|
||||||
|
data: [380, 400, 425, 450],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mttr',
|
||||||
|
name: 'MTTR (Temps Moyen de Réparation)',
|
||||||
|
value: 2.1,
|
||||||
|
unit: 'heures',
|
||||||
|
category: 'maintenance',
|
||||||
|
target: 2.5,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Mesurer la rapidité des interventions de maintenance.',
|
||||||
|
formula: 'Temps Total de Réparation / Nombre Total de Pannes',
|
||||||
|
data: [2.8, 2.5, 2.3, 2.1],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pm_cm_ratio',
|
||||||
|
name: 'Ratio Maintenance Préventive/Corrective',
|
||||||
|
value: 65,
|
||||||
|
unit: '%',
|
||||||
|
category: 'maintenance',
|
||||||
|
target: 70,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'up',
|
||||||
|
description: 'Évaluer la stratégie de maintenance (proactif vs réactif).',
|
||||||
|
formula: 'Heures MP / (Heures MP + Heures MC)',
|
||||||
|
data: [58, 61, 63, 65],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'preventive_plan',
|
||||||
|
name: 'Achèvement Plan Préventif',
|
||||||
|
value: 92,
|
||||||
|
unit: '%',
|
||||||
|
category: 'maintenance',
|
||||||
|
target: 95,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'stable',
|
||||||
|
description: 'Mesurer le respect des programmes d\'entretien.',
|
||||||
|
formula: '(Tâches MP Terminées / Tâches MP Planifiées) × 100',
|
||||||
|
data: [92, 92, 92, 92],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'maintenance_cost_per_unit',
|
||||||
|
name: 'Coût Maintenance/Unité',
|
||||||
|
value: 28,
|
||||||
|
unit: '€',
|
||||||
|
category: 'maintenance',
|
||||||
|
target: 30,
|
||||||
|
status: 'good',
|
||||||
|
trend: 'down',
|
||||||
|
description: 'Relier les dépenses de maintenance à la production.',
|
||||||
|
formula: 'Coûts Totaux de Maintenance / Nombre Total d\'Unités Produites',
|
||||||
|
data: [32, 30, 29, 28],
|
||||||
|
labels: ['Semaine 1', 'Semaine 2', 'Semaine 3', 'Semaine 4']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCategoryColor = (category: string): string => {
|
||||||
|
const colors: Record<string, string> = {
|
||||||
|
security: '#e74c3c',
|
||||||
|
quality: '#3498db',
|
||||||
|
delays: '#f39c12',
|
||||||
|
costs: '#27ae60',
|
||||||
|
maintenance: '#9b59b6'
|
||||||
|
};
|
||||||
|
return colors[category] || '#95a5a6';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCategoryName = (category: string): string => {
|
||||||
|
const names: Record<string, string> = {
|
||||||
|
security: 'Sécurité',
|
||||||
|
quality: 'Qualité',
|
||||||
|
delays: 'Délais & Livraison',
|
||||||
|
costs: 'Coûts',
|
||||||
|
maintenance: 'Maintenance'
|
||||||
|
};
|
||||||
|
return names[category] || category;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCategoryEmoji = (category: string): string => {
|
||||||
|
const emojis: Record<string, string> = {
|
||||||
|
security: '🛡️',
|
||||||
|
quality: '🎯',
|
||||||
|
delays: '⏱️',
|
||||||
|
costs: '💰',
|
||||||
|
maintenance: '🔧'
|
||||||
|
};
|
||||||
|
return emojis[category] || '📊';
|
||||||
|
};
|
||||||
25
dashboard-sqdc/src/index.css
Normal file
25
dashboard-sqdc/src/index.css
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #root {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
19
dashboard-sqdc/src/index.tsx
Normal file
19
dashboard-sqdc/src/index.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(
|
||||||
|
document.getElementById('root') as HTMLElement
|
||||||
|
);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
|
reportWebVitals();
|
||||||
1
dashboard-sqdc/src/logo.svg
Normal file
1
dashboard-sqdc/src/logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
153
dashboard-sqdc/src/pages/DetailPage.tsx
Normal file
153
dashboard-sqdc/src/pages/DetailPage.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Line } from 'react-chartjs-2';
|
||||||
|
import { KPI } from '../types';
|
||||||
|
import { getCategoryColor, getCategoryName, getCategoryEmoji } from '../data/kpiData';
|
||||||
|
import '../styles/DetailPage.css';
|
||||||
|
|
||||||
|
interface DetailPageProps {
|
||||||
|
category: 'security' | 'quality' | 'delays' | 'costs' | 'maintenance';
|
||||||
|
kpis: KPI[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DetailPage: React.FC<DetailPageProps> = ({ category, kpis }) => {
|
||||||
|
const [selectedKPI, setSelectedKPI] = React.useState<KPI | null>(kpis[0] || null);
|
||||||
|
|
||||||
|
const getStatusBadgeClass = (status: string) => {
|
||||||
|
return `badge-${status}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="detail-page">
|
||||||
|
<div className="detail-header">
|
||||||
|
<h1>
|
||||||
|
{getCategoryEmoji(category)} {getCategoryName(category)}
|
||||||
|
</h1>
|
||||||
|
<p>Analyse détaillée des indicateurs de performance</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="detail-layout">
|
||||||
|
<div className="kpi-list">
|
||||||
|
<h2>Indicateurs</h2>
|
||||||
|
<div className="kpi-list-items">
|
||||||
|
{kpis.map(kpi => (
|
||||||
|
<div
|
||||||
|
key={kpi.id}
|
||||||
|
className={`kpi-list-item ${selectedKPI?.id === kpi.id ? 'active' : ''}`}
|
||||||
|
onClick={() => setSelectedKPI(kpi)}
|
||||||
|
style={{
|
||||||
|
borderLeftColor: selectedKPI?.id === kpi.id ? getCategoryColor(category) : '#ddd'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="kpi-list-name">{kpi.name}</div>
|
||||||
|
<div className="kpi-list-value">{kpi.value} {kpi.unit}</div>
|
||||||
|
<span className={`badge ${getStatusBadgeClass(kpi.status)}`}>
|
||||||
|
{kpi.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="kpi-detail">
|
||||||
|
{selectedKPI ? (
|
||||||
|
<>
|
||||||
|
<div className="detail-header-card" style={{ borderTopColor: getCategoryColor(category) }}>
|
||||||
|
<h2>{selectedKPI.name}</h2>
|
||||||
|
<div className="detail-value-section">
|
||||||
|
<span className="detail-value">{selectedKPI.value}</span>
|
||||||
|
<span className="detail-unit">{selectedKPI.unit}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="detail-info">
|
||||||
|
<div className="info-row">
|
||||||
|
<span className="info-label">État:</span>
|
||||||
|
<span className={`badge ${getStatusBadgeClass(selectedKPI.status)}`}>
|
||||||
|
{selectedKPI.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedKPI.target && (
|
||||||
|
<div className="info-row">
|
||||||
|
<span className="info-label">Objectif:</span>
|
||||||
|
<span className="info-value">{selectedKPI.target} {selectedKPI.unit}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedKPI.trend && (
|
||||||
|
<div className="info-row">
|
||||||
|
<span className="info-label">Tendance:</span>
|
||||||
|
<span className="info-value">
|
||||||
|
{selectedKPI.trend === 'up' && '📈 En hausse'}
|
||||||
|
{selectedKPI.trend === 'down' && '📉 En baisse'}
|
||||||
|
{selectedKPI.trend === 'stable' && '➡️ Stable'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="detail-description">
|
||||||
|
<h3>Description</h3>
|
||||||
|
<p>{selectedKPI.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedKPI.formula && (
|
||||||
|
<div className="detail-formula">
|
||||||
|
<h3>Formule de Calcul</h3>
|
||||||
|
<pre>{selectedKPI.formula}</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedKPI.data && selectedKPI.labels && (
|
||||||
|
<div className="detail-chart">
|
||||||
|
<h3>Évolution sur 4 semaines</h3>
|
||||||
|
<div className="chart-container-detail">
|
||||||
|
<Line
|
||||||
|
data={{
|
||||||
|
labels: selectedKPI.labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: selectedKPI.name,
|
||||||
|
data: selectedKPI.data,
|
||||||
|
borderColor: getCategoryColor(category),
|
||||||
|
backgroundColor: `${getCategoryColor(category)}20`,
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true,
|
||||||
|
pointRadius: 6,
|
||||||
|
pointBackgroundColor: getCategoryColor(category),
|
||||||
|
pointBorderColor: '#fff',
|
||||||
|
pointBorderWidth: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: true, position: 'top' as const }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: { beginAtZero: true }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="detail-actions">
|
||||||
|
<button className="btn btn-primary">📊 Exporter</button>
|
||||||
|
<button className="btn btn-secondary">🔔 Alertes</button>
|
||||||
|
<button className="btn btn-secondary">📝 Commentaires</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="no-selection">
|
||||||
|
<p>Sélectionnez un indicateur pour voir les détails</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
211
dashboard-sqdc/src/pages/HomePage.tsx
Normal file
211
dashboard-sqdc/src/pages/HomePage.tsx
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { KPI } from '../types';
|
||||||
|
import { KPICard } from '../components/KPICard';
|
||||||
|
import { getCategoryColor } from '../data/kpiData';
|
||||||
|
import '../styles/HomePage.css';
|
||||||
|
|
||||||
|
interface HomePageProps {
|
||||||
|
kpiData: {
|
||||||
|
security: KPI[];
|
||||||
|
quality: KPI[];
|
||||||
|
delays: KPI[];
|
||||||
|
costs: KPI[];
|
||||||
|
maintenance: KPI[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HomePage: React.FC<HomePageProps> = ({ kpiData }) => {
|
||||||
|
// Fonction pour obtenir les KPI les plus importants par catégorie
|
||||||
|
const getTopKPIs = (category: KPI[]): KPI[] => {
|
||||||
|
return category.slice(0, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const topSecurityKPIs = getTopKPIs(kpiData.security);
|
||||||
|
const topQualityKPIs = getTopKPIs(kpiData.quality);
|
||||||
|
const topDelaysKPIs = getTopKPIs(kpiData.delays);
|
||||||
|
const topCostsKPIs = getTopKPIs(kpiData.costs);
|
||||||
|
const topMaintenanceKPIs = getTopKPIs(kpiData.maintenance);
|
||||||
|
|
||||||
|
// Calculer les statistiques globales
|
||||||
|
const allKPIs = [
|
||||||
|
...kpiData.security,
|
||||||
|
...kpiData.quality,
|
||||||
|
...kpiData.delays,
|
||||||
|
...kpiData.costs,
|
||||||
|
...kpiData.maintenance
|
||||||
|
];
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
total: allKPIs.length,
|
||||||
|
good: allKPIs.filter(k => k.status === 'good').length,
|
||||||
|
warning: allKPIs.filter(k => k.status === 'warning').length,
|
||||||
|
critical: allKPIs.filter(k => k.status === 'critical').length
|
||||||
|
};
|
||||||
|
|
||||||
|
const avgPerformance = Math.round(
|
||||||
|
((stats.good * 100 + stats.warning * 50) / (stats.total * 100)) * 100
|
||||||
|
);
|
||||||
|
|
||||||
|
// État pour la sélection de la plage temporelle
|
||||||
|
const [timeRange, setTimeRange] = React.useState<'day' | 'month' | 'year'>('month');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="home-page">
|
||||||
|
<div className="stats-overview">
|
||||||
|
<div className="overview-header">
|
||||||
|
<h1>📊 Dashboard SQDC</h1>
|
||||||
|
<div className="time-range-selector">
|
||||||
|
<button
|
||||||
|
className={`time-btn ${timeRange === 'day' ? 'active' : ''}`}
|
||||||
|
onClick={() => setTimeRange('day')}
|
||||||
|
>
|
||||||
|
Jour
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`time-btn ${timeRange === 'month' ? 'active' : ''}`}
|
||||||
|
onClick={() => setTimeRange('month')}
|
||||||
|
>
|
||||||
|
Mois
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`time-btn ${timeRange === 'year' ? 'active' : ''}`}
|
||||||
|
onClick={() => setTimeRange('year')}
|
||||||
|
>
|
||||||
|
Année
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stats-grid">
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="stat-value" style={{ color: '#27ae60' }}>
|
||||||
|
{stats.good}
|
||||||
|
</div>
|
||||||
|
<div className="stat-label">KPI Bon</div>
|
||||||
|
<div className="stat-description">
|
||||||
|
{Math.round((stats.good / stats.total) * 100)}% des KPI
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="stat-value" style={{ color: '#f39c12' }}>
|
||||||
|
{stats.warning}
|
||||||
|
</div>
|
||||||
|
<div className="stat-label">À Améliorer</div>
|
||||||
|
<div className="stat-description">
|
||||||
|
{Math.round((stats.warning / stats.total) * 100)}% des KPI
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="stat-value" style={{ color: '#e74c3c' }}>
|
||||||
|
{stats.critical}
|
||||||
|
</div>
|
||||||
|
<div className="stat-label">Critique</div>
|
||||||
|
<div className="stat-description">
|
||||||
|
{Math.round((stats.critical / stats.total) * 100)}% des KPI
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="stat-value" style={{ color: '#3498db' }}>
|
||||||
|
{avgPerformance}%
|
||||||
|
</div>
|
||||||
|
<div className="stat-label">Performance Globale</div>
|
||||||
|
<div className="stat-description">Indicateur synthétique</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="top-kpis-section">
|
||||||
|
<h2>🎯 KPI Clés par Catégorie</h2>
|
||||||
|
|
||||||
|
<div className="categories-grid">
|
||||||
|
{/* BLOC SYNTHÈSE */}
|
||||||
|
<div className="summary-section">
|
||||||
|
<h3>📋 Alertes Prioritaires</h3>
|
||||||
|
<div className="alerts-list">
|
||||||
|
<div className="alert-item critical">
|
||||||
|
<span className="alert-icon">🔴</span>
|
||||||
|
<div className="alert-content">
|
||||||
|
<div className="alert-title">Coût des Non-Qualité</div>
|
||||||
|
<div className="alert-value">18 500 €</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="alert-item warning">
|
||||||
|
<span className="alert-icon">⚠️</span>
|
||||||
|
<div className="alert-content">
|
||||||
|
<div className="alert-title">Taux de Retouche</div>
|
||||||
|
<div className="alert-value">3.8%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="alert-item warning">
|
||||||
|
<span className="alert-icon">⚠️</span>
|
||||||
|
<div className="alert-content">
|
||||||
|
<div className="alert-title">Downtime</div>
|
||||||
|
<div className="alert-value">2.5h/jour</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="alert-item warning">
|
||||||
|
<span className="alert-icon">⚠️</span>
|
||||||
|
<div className="alert-content">
|
||||||
|
<div className="alert-title">OEE</div>
|
||||||
|
<div className="alert-value">72.3%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="score-section">
|
||||||
|
<div className="score-label">Performance Globale</div>
|
||||||
|
<div className="score-circle">{avgPerformance}%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="category-section">
|
||||||
|
<h3 style={{ color: getCategoryColor('security') }}>🛡️ Sécurité</h3>
|
||||||
|
<div className="kpis-grid">
|
||||||
|
{topSecurityKPIs.map(kpi => (
|
||||||
|
<KPICard key={kpi.id} kpi={kpi} color={getCategoryColor('security')} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="category-section">
|
||||||
|
<h3 style={{ color: getCategoryColor('quality') }}>🎯 Qualité</h3>
|
||||||
|
<div className="kpis-grid">
|
||||||
|
{topQualityKPIs.map(kpi => (
|
||||||
|
<KPICard key={kpi.id} kpi={kpi} color={getCategoryColor('quality')} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="category-section">
|
||||||
|
<h3 style={{ color: getCategoryColor('delays') }}>⏱️ Délais & Livraison</h3>
|
||||||
|
<div className="kpis-grid">
|
||||||
|
{topDelaysKPIs.map(kpi => (
|
||||||
|
<KPICard key={kpi.id} kpi={kpi} color={getCategoryColor('delays')} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="category-section">
|
||||||
|
<h3 style={{ color: getCategoryColor('costs') }}>💰 Coûts</h3>
|
||||||
|
<div className="kpis-grid">
|
||||||
|
{topCostsKPIs.map(kpi => (
|
||||||
|
<KPICard key={kpi.id} kpi={kpi} color={getCategoryColor('costs')} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="category-section">
|
||||||
|
<h3 style={{ color: getCategoryColor('maintenance') }}>🔧 Maintenance</h3>
|
||||||
|
<div className="kpis-grid">
|
||||||
|
{topMaintenanceKPIs.map(kpi => (
|
||||||
|
<KPICard key={kpi.id} kpi={kpi} color={getCategoryColor('maintenance')} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
dashboard-sqdc/src/react-app-env.d.ts
vendored
Normal file
1
dashboard-sqdc/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
||||||
15
dashboard-sqdc/src/reportWebVitals.ts
Normal file
15
dashboard-sqdc/src/reportWebVitals.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { ReportHandler } from 'web-vitals';
|
||||||
|
|
||||||
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
|
getCLS(onPerfEntry);
|
||||||
|
getFID(onPerfEntry);
|
||||||
|
getFCP(onPerfEntry);
|
||||||
|
getLCP(onPerfEntry);
|
||||||
|
getTTFB(onPerfEntry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reportWebVitals;
|
||||||
5
dashboard-sqdc/src/setupTests.ts
Normal file
5
dashboard-sqdc/src/setupTests.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
17
dashboard-sqdc/src/styles/Charts.css
Normal file
17
dashboard-sqdc/src/styles/Charts.css
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.chart-container {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container h2 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
position: relative;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
290
dashboard-sqdc/src/styles/DetailPage.css
Normal file
290
dashboard-sqdc/src/styles/DetailPage.css
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
.detail-page {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header p {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KPI LIST */
|
||||||
|
.kpi-list {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
color: #333;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list h2 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list-item {
|
||||||
|
padding: 1rem;
|
||||||
|
border-left: 3px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list-item:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list-item.active {
|
||||||
|
background: #f0f2ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list-name {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list-value {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-good {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-critical {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DETAIL SECTION */
|
||||||
|
.kpi-detail {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header-card {
|
||||||
|
border-top: 4px solid;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header-card h2 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-unit {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* INFO SECTION */
|
||||||
|
.detail-info {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DESCRIPTION & FORMULA */
|
||||||
|
.detail-description,
|
||||||
|
.detail-formula {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-description h3,
|
||||||
|
.detail-formula h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-description p {
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-formula pre {
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CHART DETAIL */
|
||||||
|
.detail-chart {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-chart h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container-detail {
|
||||||
|
position: relative;
|
||||||
|
height: 350px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ACTIONS */
|
||||||
|
.detail-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #764ba2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-selection {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem;
|
||||||
|
color: #999;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESPONSIVE */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.detail-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-list-items {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.detail-header h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
257
dashboard-sqdc/src/styles/HomePage.css
Normal file
257
dashboard-sqdc/src/styles/HomePage.css
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
.home-page {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* STATS OVERVIEW */
|
||||||
|
.stats-overview {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-header h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TIME RANGE SELECTOR */
|
||||||
|
.time-range-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-btn.active {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-description {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOP KPIs SECTION */
|
||||||
|
.top-kpis-section {
|
||||||
|
background: rgba(255, 255, 255, 0.98);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-kpis-section h2 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CATEGORIES GRID - CÔTE À CÔTE */
|
||||||
|
.categories-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SUMMARY SECTION */
|
||||||
|
.summary-section {
|
||||||
|
padding: 1rem;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #e8ebf0 100%);
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-section h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alerts-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.6rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
max-height: 180px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
padding: 0.7rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
border-left: 3px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-item.critical {
|
||||||
|
border-left-color: #e74c3c;
|
||||||
|
background: rgba(231, 76, 60, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-item.warning {
|
||||||
|
border-left-color: #f39c12;
|
||||||
|
background: rgba(243, 156, 18, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-value {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-section {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.8rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-circle {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-section {
|
||||||
|
padding: 1rem;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 4px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-section h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpis-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESPONSIVE */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.kpis-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.categories-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.overview-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-header h1 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-kpis-section {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categories-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpis-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
97
dashboard-sqdc/src/styles/KPICard.css
Normal file
97
dashboard-sqdc/src/styles/KPICard.css
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
.kpi-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 1.2rem;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
padding-bottom: 0.8rem;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-title {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-trend {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-value-section {
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-value {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-unit {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-status.status-good {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-status.status-warning {
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-status.status-critical {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-target {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpi-description {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
31
dashboard-sqdc/src/types/index.ts
Normal file
31
dashboard-sqdc/src/types/index.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Types pour les KPI et données
|
||||||
|
export interface KPI {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: number | string;
|
||||||
|
unit: string;
|
||||||
|
category: 'security' | 'quality' | 'delays' | 'costs' | 'maintenance';
|
||||||
|
target?: number;
|
||||||
|
status: 'good' | 'warning' | 'critical';
|
||||||
|
trend?: 'up' | 'down' | 'stable';
|
||||||
|
description: string;
|
||||||
|
formula?: string;
|
||||||
|
data?: number[];
|
||||||
|
labels?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryData {
|
||||||
|
security: KPI[];
|
||||||
|
quality: KPI[];
|
||||||
|
delays: KPI[];
|
||||||
|
costs: KPI[];
|
||||||
|
maintenance: KPI[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardStats {
|
||||||
|
totalKPIs: number;
|
||||||
|
goodCount: number;
|
||||||
|
warningCount: number;
|
||||||
|
criticalCount: number;
|
||||||
|
avgPerformance: number;
|
||||||
|
}
|
||||||
26
dashboard-sqdc/tsconfig.json
Normal file
26
dashboard-sqdc/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
Catégorie,KPI (Indicateur),Objectif,Formule de Calcul Ajustée
|
||||||
|
SÉCURITÉ,Taux de Fréquence (TF),Mesurer la fréquence des accidents avec arrêt.,(Nombre d’Accidents avec Arret/Nombre d’Heures Travaillees)×1000000
|
||||||
|
SÉCURITÉ,Nombre d'Incidents/Near Miss,Évaluer la culture de sécurité et la proactivité.,Compte des rapports d’incidents (sans blessure/dommage)
|
||||||
|
SÉCURITÉ,Taux de Conformité aux Audits,Mesurer le respect des procédures de sécurité.,(Points de Controle Conformites/Total Points de Controle)×100
|
||||||
|
QUALITÉ,Taux de Rebut (Scrap Rate),Mesurer le pourcentage d'unités jetées (irréparables).,(Nombre d’Unites Rebutees/Nombre Total d’Unites Produites)×100
|
||||||
|
QUALITÉ,Taux de Retouche (Rework Rate),Mesurer le pourcentage d'unités nécessitant une reprise.,(Nombre d’Unites Retouchees/Nombre Total d’Unites Produites)×100
|
||||||
|
QUALITÉ,Nombre de Défauts par Unité (DPU),Mesurer le nombre moyen de défauts par produit.,Nombre Total de Defauts Trouves/Nombre Total d’Unites Inspectees
|
||||||
|
QUALITÉ,Taux de Retours Clients,Mesurer l'impact de la non-qualité chez le client.,(Nombre d’Unites Retournees/Nombre Total d’Unites Vendues)×100
|
||||||
|
QUALITÉ,Taux de rendement synthétique (TRS),,Nb pièces bonnes x Temps cycle / Temps d’ouverture
|
||||||
|
DÉLAIS / LIVRAISON,Efficacité Globale de l'Équipement (OEE),"Mesurer l'efficacité combinée (Disponibilité, Performance, Qualité).",Disponibilite×Performance×Qualite
|
||||||
|
DÉLAIS / LIVRAISON,Taux de Respect du Plan (Schedule Adherence),Mesurer la capacité à atteindre le volume planifié.,(Quantite Reellement Produite/Quantite Planifiee)×100
|
||||||
|
DÉLAIS / LIVRAISON,"Temps de Cycle (Cycle Time, TC)",Mesurer le temps nécessaire pour assembler une unité.,Temps Total de Production/Nombre Total d’Unites Produites
|
||||||
|
DÉLAIS / LIVRAISON,Tack Time (TT),,Temps de production / Nombre de pièces demandées
|
||||||
|
DÉLAIS / LIVRAISON,Temps d'Arrêt Imprévu (Downtime),Mesurer le temps d'arrêt non planifié de la ligne.,Somme des Periodes d’Arret Non Planifie
|
||||||
|
COÛT,Coût par Unité (CPU),Mesurer l'efficacité des coûts de production.,Cout Total de Production/Nombre Total d’Unites Produites
|
||||||
|
COÛT,Productivité de la Main-d'œuvre,Mesurer l'efficacité de l'équipe d'assemblage.,Nombre d’Unites Produites/Total Heures Main-d’œuvre Directe
|
||||||
|
COÛT,Coût des Non-Qualité (CNQ),"Mesurer le coût des défauts (retouche, rebut, retours).",Cout des Rebuts+Cout des Retouches
|
||||||
|
MAINTENANCE,Temps Moyen Entre Pannes (MTBF),Mesurer la fiabilité des équipements.,Temps Total de Fonctionnement/Nombre Total de Pannes
|
||||||
|
MAINTENANCE,Temps Moyen de Réparation (MTTR),Mesurer la rapidité des interventions de maintenance.,Temps Total de Reparation/Nombre Total de Pannes
|
||||||
|
MAINTENANCE,Ratio Maintenance Préventive / Corrective,Évaluer la stratégie de maintenance (proactif vs réactif).,Heures MP/Heures MC
|
||||||
|
MAINTENANCE,Taux d'Achèvement du Plan Préventif,Mesurer le respect des programmes d'entretien.,(Taches MP Terminees/Taches MP Planifiees)×100
|
||||||
|
MAINTENANCE,Coût de Maintenance par Unité Produite,Relier les dépenses de maintenance à la production.,Couts Totaux de Maintenance/Nombre Total d’Unites Produites
|
||||||
|
Loading…
x
Reference in New Issue
Block a user