|
@@ -788,27 +785,27 @@ const AuditCalendar: React.FC = () => {
event.auditType === 'INTERNAL' ? 'bg-gradient-to-b from-blue-400 to-blue-600' : 'bg-gradient-to-b from-red-400 to-red-600'
}`}>
- {event.title}
- {event.auditType}
+ {event.title}
+ {event.auditType}
|
{event.status}
|
-
+
{format(new Date(event.start), 'MMM d, yyyy')}
-
+
{format(new Date(event.start), 'HH:mm')} -
{event.end ?
format(new Date(event.end), ' HH:mm') :
@@ -817,14 +814,14 @@ const AuditCalendar: React.FC = () => {
|
-
+
-
+
{event.auditor.name}
-
+
{event.auditor.isExternal ? 'External' : 'Internal'}
@@ -833,11 +830,11 @@ const AuditCalendar: React.FC = () => {
{event.department ? (
-
- {event.department}
+
+ {event.department}
) : (
- Not specified
+ Not specified
)}
|
@@ -846,7 +843,7 @@ const AuditCalendar: React.FC = () => {
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => setSelectedEvent(event)}
- className="text-blue-600 hover:text-blue-800 p-2 rounded-lg hover:bg-blue-100 transition-all"
+ className="text-primary hover:opacity-80 p-2 rounded-lg hover:bg-primary/10 transition-all"
>
View
@@ -854,7 +851,7 @@ const AuditCalendar: React.FC = () => {
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => navigate(`/audits/${event.id}`)}
- className="text-gray-600 hover:text-gray-800 p-2 rounded-lg hover:bg-gray-100 transition-all"
+ className="text-muted-foreground hover:text-foreground p-2 rounded-lg hover:bg-muted transition-all"
>
@@ -875,41 +872,41 @@ const AuditCalendar: React.FC = () => {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}
- className="px-8 py-6 bg-gradient-to-r from-gray-50 to-blue-50 border-t border-gray-200"
+ className="px-8 py-6 bg-muted/50 border-t border-border"
>
-
-
+
+
Legend
- Planned
+ Planned
- In Progress
+ In Progress
- Completed
+ Completed
- Cancelled
+ Cancelled
- Delayed
+ Delayed
-
- Internal
+
+ Internal
-
- External
+
+ External
@@ -929,10 +926,10 @@ const AuditCalendar: React.FC = () => {
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.8, opacity: 0, y: 50 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
- className="bg-white rounded-2xl shadow-2xl max-w-lg w-full overflow-hidden border border-gray-200"
+ className="bg-card rounded-2xl shadow-2xl max-w-lg w-full overflow-hidden border border-border"
>
{/* Enhanced Modal Header */}
-
+
@@ -943,7 +940,7 @@ const AuditCalendar: React.FC = () => {
whileHover={{ scale: 1.1, rotate: 90 }}
whileTap={{ scale: 0.9 }}
onClick={() => setSelectedEvent(null)}
- className="text-white/80 hover:text-white rounded-full h-10 w-10 flex items-center justify-center hover:bg-white/20 transition-all"
+ className="text-primary-foreground/80 hover:text-primary-foreground rounded-full h-10 w-10 flex items-center justify-center hover:bg-white/20 transition-all"
>
@@ -954,24 +951,24 @@ const AuditCalendar: React.FC = () => {
- {selectedEvent.title}
+ {selectedEvent.title}
{selectedEvent.auditType}
{selectedEvent.status}
@@ -981,19 +978,19 @@ const AuditCalendar: React.FC = () => {
-
+
{/* Date & Time */}
-
-
+
+
- Date & Time
-
+ Date & Time
+
{format(new Date(selectedEvent.start), 'EEEE, MMMM d, yyyy')}
-
+
{format(new Date(selectedEvent.start), 'HH:mm')}
{selectedEvent.end && ` - ${format(new Date(selectedEvent.end), 'HH:mm')}`}
@@ -1002,15 +999,15 @@ const AuditCalendar: React.FC = () => {
{/* Auditor */}
-
-
+
+
- Auditor
-
+ Auditor
+
{selectedEvent.auditor.name}
{selectedEvent.auditor.isExternal && (
-
+
External
)}
@@ -1021,12 +1018,12 @@ const AuditCalendar: React.FC = () => {
{/* Auditee */}
{selectedEvent.auditee && (
-
-
+
+
- Auditee
- {selectedEvent.auditee.name}
+ Auditee
+ {selectedEvent.auditee.name}
)}
@@ -1034,12 +1031,12 @@ const AuditCalendar: React.FC = () => {
{/* Department */}
{selectedEvent.department && (
-
-
+
+
- Department
- {selectedEvent.department}
+ Department
+ {selectedEvent.department}
)}
@@ -1047,12 +1044,12 @@ const AuditCalendar: React.FC = () => {
{/* Enhanced Modal Footer */}
-
+
setSelectedEvent(null)}
- className="px-6 py-3 border border-gray-300 rounded-xl bg-white text-gray-700 hover:bg-gray-50 text-sm font-medium transition-all shadow-sm"
+ className="px-6 py-3 border border-border rounded-xl bg-card text-foreground hover:bg-muted text-sm font-medium transition-all shadow-sm"
>
Close
@@ -1060,7 +1057,7 @@ const AuditCalendar: React.FC = () => {
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={viewAuditDetails}
- className="px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 text-sm font-medium transition-all shadow-lg flex items-center"
+ className="px-6 py-3 bg-primary text-primary-foreground rounded-xl hover:opacity-90 text-sm font-medium transition-all shadow-lg flex items-center"
>
View Full Details
@@ -1075,4 +1072,4 @@ const AuditCalendar: React.FC = () => {
);
};
-export default AuditCalendar;
\ No newline at end of file
+export default AuditCalendar;
diff --git a/client/src/components/pages/Audit/Auditmanage.tsx b/client/src/components/pages/Audit/Auditmanage.tsx
index 30b6533..234ea0c 100644
--- a/client/src/components/pages/Audit/Auditmanage.tsx
+++ b/client/src/components/pages/Audit/Auditmanage.tsx
@@ -55,21 +55,17 @@ const Auditmanage: React.FC = () => {
// Only render tabs if we're on the main audit management pages
const shouldShowTabs = path === '/audits' || path === '/audits/' || path === '/audits/new';
- // Tab definitions with beautiful icons and styling
+ // Tab definitions with theme-aware styling
const tabs = [
{
id: 'list',
label: 'Audit List',
icon:
,
- gradient: 'from-blue-600 to-purple-600',
- shadow: 'shadow-blue-500/25',
},
{
id: 'create',
label: 'Create Audit',
icon: ,
- gradient: 'from-emerald-600 to-teal-600',
- shadow: 'shadow-emerald-500/25',
},
];
@@ -90,11 +86,11 @@ const Auditmanage: React.FC = () => {
}
return (
-
+
{/* Beautiful Tab Navigation */}
-
+
{tabs.map((tab, index) => (
{
{activeTab === tab.id && (
)}
{/* Hover effect */}
{/* Tab content */}
@@ -126,8 +122,8 @@ const Auditmanage: React.FC = () => {
{
{tab.label}
@@ -156,7 +152,7 @@ const Auditmanage: React.FC = () => {
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.2 }}
>
-
+
)}
@@ -195,4 +191,4 @@ const Auditmanage: React.FC = () => {
);
};
-export default Auditmanage;
\ No newline at end of file
+export default Auditmanage;
diff --git a/client/src/components/pages/Auth/Login.tsx b/client/src/components/pages/Auth/Login.tsx
index 7d80d29..d839681 100755
--- a/client/src/components/pages/Auth/Login.tsx
+++ b/client/src/components/pages/Auth/Login.tsx
@@ -4,7 +4,10 @@ import { useMutation } from "@tanstack/react-query";
import api from "../../../utils/api";
import { API_ROUTES } from "../../../utils/api";
import logo from "../../../assets/logo12.png"
-import { Lock, Mail, Check, Rocket, ArrowRight, Zap, Shield, ChevronLeft, ChevronRight } from "lucide-react"
+import batchImg from "../../../assets/batch.png"
+import qualityImg from "../../../assets/quality.png"
+import trainingImg from "../../../assets/training.png"
+import { Lock, Mail, Check, Rocket, ArrowRight, ChevronLeft, ChevronRight } from "lucide-react"
interface LoginResponse {
token?: string
@@ -64,20 +67,17 @@ const Login = () => {
{
title: "Batch\nManagement",
desc: "Simplify batch creation, tracking, and documentation with our intuitive interface.",
- image:
- "https://videos.openai.com/az/vg-assets/task_01keehkt24e13b4fgcjb1k4k5x%2F1767867313_img_1.webp?se=2026-01-13T00%3A00%3A00Z&sp=r&sv=2024-08-04&sr=b&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2026-01-08T03%3A55%3A27Z&ske=2026-01-15T04%3A00%3A27Z&sks=b&skv=2024-08-04&sig=uga4yHvWpuKOVfnO0Trv%2BaOekYZobIJ2GsHSv8ggtDE%3D&ac=oaivgprodscus2",
+ image: batchImg ,
},
{
title: "Streamline Quality\nControl",
desc: "End-to-end batch workflows with traceability and audit-ready reports.",
- image:
- "https://videos.openai.com/az/vg-assets/task_01keehyt2dexdtpjgv52rmcm25%2F1767867669_img_2.webp?se=2026-01-13T00%3A00%3A00Z&sp=r&sv=2024-08-04&sr=b&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2026-01-08T03%3A51%3A56Z&ske=2026-01-15T03%3A56%3A56Z&sks=b&skv=2024-08-04&sig=Or2SgUr1xJZmnF16bty4DLcxJafmHppQfRV%2BSM9X/vI%3D&ac=oaivgprodscus2",
+ image: qualityImg,
},
{
title: "Training & Compliance",
desc: "Centralized training modules and attendance tracking for consistent operations.",
- image:
- "https://videos.openai.com/az/vg-assets/task_01keej1m2pfe9vtkkb4tg9gp3x%2F1767867760_img_1.webp?se=2026-01-13T00%3A00%3A00Z&sp=r&sv=2024-08-04&sr=b&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2026-01-08T03%3A55%3A09Z&ske=2026-01-15T04%3A00%3A09Z&sks=b&skv=2024-08-04&sig=e7SQ8SxWUbbgFg4YNdW6NL9SiETplcDsUwyOnl4Pr/I%3D&ac=oaivgprodscus2",
+ image: trainingImg,
},
]
const [currentSlide, setCurrentSlide] = useState(0)
@@ -140,119 +140,98 @@ const Login = () => {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {slides[currentSlide].title}
-
- {slides[currentSlide].desc}
+
+
+
-
-
-
-
-
- {slides.map((_, i) => (
-
-
+
+ {slides[currentSlide].title}
+
+ {slides[currentSlide].desc}
+
+
+
+
+
+
+
+ {slides.map((_, i) => (
+
-
- {currentSlide + 1} / {slides.length}
+
-
-
+
+ {currentSlide + 1} / {slides.length}
+
+
@@ -271,7 +250,7 @@ const Login = () => {
- );
-};
+ );
+ };
const BatchVerification: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
@@ -634,39 +621,29 @@ const BatchVerification: React.FC = () => {
if (error) {
return (
-
-
+
+
-
+
Error Loading Batches
-
+
Failed to load batches for verification
- refetch()}
- className="px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all font-medium"
+ className="px-6 py-3 rounded transition-colors font-medium"
+ style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}
>
Try Again
-
-
+
+
);
}
return (
-
+
{/* Show details view if selected */}
{selectedBatchId ? (
@@ -674,14 +651,11 @@ const BatchVerification: React.FC = () => {
{parametersLoading ? (
{[...Array(3)].map((_, i) => (
-
-
+
+
{[...Array(4)].map((_, j) => (
-
+
))}
@@ -690,41 +664,37 @@ const BatchVerification: React.FC = () => {
) : parametersData ? (
{/* Enhanced Batch Info */}
-
-
+
+
-
-
-
+
+
Back
-
-
-
+
+
-
+
{parametersData.batch.batchNumber}
-
+
{parametersData.batch.product.name}
-
+
Total Parameters
-
+
{parametersData.totalParameters}
@@ -733,13 +703,13 @@ const BatchVerification: React.FC = () => {
-
-
+
+
-
+
Production Date
-
+
{new Date(
parametersData.batch.dateOfProduction
).toLocaleDateString()}
@@ -747,70 +717,70 @@ const BatchVerification: React.FC = () => {
-
-
+
+
-
+
Best Before Date
-
+
{parametersData.batch.bestBeforeDate
? new Date(
- parametersData.batch.bestBeforeDate
- ).toLocaleDateString()
+ parametersData.batch.bestBeforeDate
+ ).toLocaleDateString()
: 'N/A'}
-
-
+
+
-
+
Maker
-
+
{parametersData.batch.maker.name}
-
-
+
+
-
+
Sample Analysis Started
-
+
{parametersData.batch.sampleAnalysisStarted
? new Date(
- parametersData.batch.sampleAnalysisStarted
- ).toLocaleDateString()
+ parametersData.batch.sampleAnalysisStarted
+ ).toLocaleDateString()
: 'N/A'}
-
-
+
+
-
+
Sample Analysis Completed
-
+
{parametersData.batch.sampleAnalysisCompleted
? new Date(
- parametersData.batch.sampleAnalysisCompleted
- ).toLocaleDateString()
+ parametersData.batch.sampleAnalysisCompleted
+ ).toLocaleDateString()
: 'In Progress'}
-
-
+
+
-
+
Status
@@ -831,22 +801,21 @@ const BatchVerification: React.FC = () => {
if (isVerified) {
return (
-
Export Certificate of Analysis
-
+
);
}
return null;
})()}
-
+
{/* Show verification status if batch is verified */}
{(() => {
@@ -858,51 +827,25 @@ const BatchVerification: React.FC = () => {
if (isVerified) {
return (
-
+
-
+
{selectedBatch?.status === 'APPROVED' ? (
-
+
) : (
-
+
)}
-
- Batch{' '}
- {selectedBatch?.status === 'APPROVED'
- ? 'Approved'
- : 'Rejected'}
+
+ Batch {selectedBatch?.status === 'APPROVED' ? 'Approved' : 'Rejected'}
This batch has been{' '}
{selectedBatch?.status?.toLowerCase()} and
@@ -916,7 +859,7 @@ const BatchVerification: React.FC = () => {
-
+
);
}
return null;
@@ -924,7 +867,7 @@ const BatchVerification: React.FC = () => {
{/* Enhanced Parameters by Category */}
{Object.entries(parametersData.parametersByCategory).map(
- ([category, parameters], categoryIndex) => {
+ ([category, parameters]) => {
const selectedBatch = batches.find(
(b) => b.id === selectedBatchId
);
@@ -932,41 +875,29 @@ const BatchVerification: React.FC = () => {
selectedBatch && isBatchVerified(selectedBatch);
return (
-
-
+
+
-
-
+
+
-
+
{category}
-
+
{(parameters as any[]).length} parameters
{isVerified ? ' (verified)' : ' to verify'}
-
+
{(parameters as any[]).length}
tests
-
+
@@ -979,7 +910,7 @@ const BatchVerification: React.FC = () => {
isDisabled={isVerified}
/>
-
+
);
}
)}
@@ -994,20 +925,17 @@ const BatchVerification: React.FC = () => {
if (!isVerified) {
return (
-
-
+
+
-
-
+
-
+
Complete Verification
-
+
Save test results and make final decision
@@ -1016,47 +944,41 @@ const BatchVerification: React.FC = () => {
-
Save Progress
-
+
- {
- const remarks = prompt(
- 'Enter rejection remarks:'
- );
- if (remarks)
- handleCompleteBatch('REJECT', remarks);
+ const remarks = prompt('Enter rejection remarks:');
+ if (remarks) handleCompleteBatch('REJECT', remarks);
}}
disabled={completeBatchMutation.isPending}
- className="px-8 py-4 bg-gradient-to-r from-red-600 to-rose-600 text-white rounded-xl hover:from-red-700 hover:to-rose-700 disabled:opacity-50 transition-all shadow-lg hover:shadow-xl flex items-center gap-3 font-semibold text-lg"
- whileHover={{ scale: 1.05 }}
- whileTap={{ scale: 0.95 }}
+ className="px-8 py-4 rounded disabled:opacity-50 transition-colors flex items-center gap-3 font-semibold text-lg"
+ style={{ background: 'var(--destructive)', color: 'var(--destructive-foreground)' }}
>
Reject Batch
-
+
- handleCompleteBatch('APPROVE')}
disabled={completeBatchMutation.isPending}
- className="px-8 py-4 bg-gradient-to-r from-green-600 to-emerald-600 text-white rounded-xl hover:from-green-700 hover:to-emerald-700 disabled:opacity-50 transition-all shadow-lg hover:shadow-xl flex items-center gap-3 font-semibold text-lg"
- whileHover={{ scale: 1.05 }}
- whileTap={{ scale: 0.95 }}
+ className="px-8 py-4 rounded disabled:opacity-50 transition-colors flex items-center gap-3 font-semibold text-lg"
+ style={{ background: 'var(--success, #16a34a)', color: '#fff' }}
>
Approve Batch
-
+
-
+
);
}
return null;
@@ -1066,32 +988,23 @@ const BatchVerification: React.FC = () => {
) : (
/* Main Container */
-
+
{/* Header Section */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Batch Verification
-
-
- Review and verify quality parameters for submitted
- batches
-
-
+
+
+
+
+
+
+
+
+
+ Batch Verification
+
+
+ Review and verify quality parameters for submitted
+ batches
+
@@ -1099,271 +1012,234 @@ const BatchVerification: React.FC = () => {
{/* Search and Filters Section */}
-
+
- setIsFilterOpen(!isFilterOpen)}
- className="flex items-center gap-2 px-4 py-2.5 border border-gray-200 bg-white rounded-lg hover:bg-gray-50 transition-colors duration-200 shadow-sm text-sm"
- whileHover={{ scale: 1.02 }}
- whileTap={{ scale: 0.98 }}
+ className="flex items-center gap-2 px-4 py-2.5 rounded transition-colors text-sm"
+ style={{ border: '1px solid var(--border)', background: 'var(--card)', color: 'var(--foreground)' }}
>
-
- Filters
-
-
-
-
+
+ Filters
+
+
- refetch()}
- className="flex items-center gap-2 px-4 py-2.5 border border-gray-200 bg-white rounded-lg hover:bg-gray-50 transition-colors duration-200 shadow-sm text-sm"
- whileHover={{ scale: 1.02 }}
- whileTap={{ scale: 0.98 }}
+ className="flex items-center gap-2 px-4 py-2.5 rounded transition-colors text-sm"
+ style={{ border: '1px solid var(--border)', background: 'var(--card)', color: 'var(--foreground)' }}
>
-
- Refresh
-
+
+ Refresh
+
{/* Updated Filter Section */}
-
- {isFilterOpen && (
-
-
-
-
-
-
-
-
+ {isFilterOpen && (
+
+
+
+
+
+
+
-
- {
- setSearchTerm('');
- setFilterStatus('all');
- }}
- className="px-4 py-2 border border-gray-200 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors duration-200 text-sm"
- whileHover={{ scale: 1.02 }}
- whileTap={{ scale: 0.98 }}
- >
-
-
- Clear
-
-
- setIsFilterOpen(false)}
- className="px-4 py-2 bg-blue-600 text-white rounded-lg shadow-md text-sm"
- whileHover={{ scale: 1.02 }}
- whileTap={{ scale: 0.98 }}
- >
-
-
- Apply
-
-
+
+
-
- )}
-
+
+
+
+
+ )}
{/* Table Section */}
{isLoading ? (
) : filteredBatches.length === 0 ? (
-
-
-
+
+
+
-
+
No batches found
-
+
No batches are currently available for verification or match
your search criteria.
) : (
-
+
-
+
|
Batch Number
|
Product
|
Production Date
|
Maker
|
Verification Status
|
Parameters
|
Actions
|
-
+
{filteredBatches.map(
- (batch: BatchForVerification, index: number) => (
-
-
+ (batch: BatchForVerification) => (
+ |
+ |
{batch.batchNumber}
|
-
+ |
|
-
+ |
{formatDate(batch.dateOfProduction)}
|
-
+ |
{batch.maker.name}
|
|
-
+ |
-
+
{batch.totalParameters} parameters
|
- setSelectedBatchId(batch.id)}
- className={`px-3 py-2 rounded-lg transition-colors flex items-center gap-1 ml-auto ${
- isBatchVerified(batch)
- ? 'text-gray-600 hover:text-gray-800 bg-gray-50 hover:bg-gray-100'
- : 'text-blue-600 hover:text-blue-800 bg-blue-50 hover:bg-blue-100'
- }`}
- whileHover={{ scale: 1.05 }}
- whileTap={{ scale: 0.95 }}
- title={
- isBatchVerified(batch)
- ? 'View Details'
- : 'Start Verification'
+ style={isBatchVerified(batch)
+ ? { color: 'var(--muted-foreground)', background: 'var(--muted)' }
+ : undefined
}
+ className={`px-3 py-2 rounded transition-colors flex items-center gap-1 ml-auto ${!isBatchVerified(batch) ? 'verify-glow-btn' : ''}`}
+ title={isBatchVerified(batch) ? 'View Details' : 'Start Verification'}
>
{isBatchVerified(batch) ? (
<>
-
+
View
>
) : (
<>
-
-
+
+
Verify
>
)}
-
+
|
-
+
)
)}
@@ -1371,10 +1247,10 @@ const BatchVerification: React.FC = () => {
)}
-
+
)}
-
+
);
};
diff --git a/client/src/components/pages/Dashboard/auditdashboard.tsx b/client/src/components/pages/Dashboard/auditdashboard.tsx
index 30bff4e..3804587 100644
--- a/client/src/components/pages/Dashboard/auditdashboard.tsx
+++ b/client/src/components/pages/Dashboard/auditdashboard.tsx
@@ -176,26 +176,26 @@ const cardVariants = {
}
};
-// Status color mapping
+// Status color mapping - theme aware
const getStatusColor = (status: string) => {
const colors = {
- 'COMPLETED': 'text-green-600 bg-green-100',
- 'IN_PROGRESS': 'text-blue-600 bg-blue-100',
- 'PLANNED': 'text-yellow-600 bg-yellow-100',
- 'DRAFT': 'text-gray-600 bg-gray-100',
- 'CANCELLED': 'text-red-600 bg-red-100'
+ 'COMPLETED': 'text-green-700 bg-green-100 dark:bg-green-900/40 dark:text-green-300',
+ 'IN_PROGRESS': 'text-blue-700 bg-blue-100 dark:bg-blue-900/40 dark:text-blue-300',
+ 'PLANNED': 'text-yellow-700 bg-yellow-100 dark:bg-yellow-900/40 dark:text-yellow-300',
+ 'DRAFT': 'text-gray-700 bg-gray-100 dark:bg-gray-700/40 dark:text-gray-300',
+ 'CANCELLED': 'text-red-700 bg-red-100 dark:bg-red-900/40 dark:text-red-300'
};
- return colors[status as keyof typeof colors] || 'text-gray-600 bg-gray-100';
+ return colors[status as keyof typeof colors] || 'text-gray-700 bg-gray-100 dark:bg-gray-700/40 dark:text-gray-300';
};
const getPriorityColor = (priority: string) => {
const colors = {
- 'CRITICAL': 'text-red-600 bg-red-100',
- 'HIGH': 'text-orange-600 bg-orange-100',
- 'MEDIUM': 'text-yellow-600 bg-yellow-100',
- 'LOW': 'text-green-600 bg-green-100'
+ 'CRITICAL': 'text-red-700 bg-red-100 dark:bg-red-900/40 dark:text-red-300',
+ 'HIGH': 'text-orange-700 bg-orange-100 dark:bg-orange-900/40 dark:text-orange-300',
+ 'MEDIUM': 'text-yellow-700 bg-yellow-100 dark:bg-yellow-900/40 dark:text-yellow-300',
+ 'LOW': 'text-green-700 bg-green-100 dark:bg-green-900/40 dark:text-green-300'
};
- return colors[priority as keyof typeof colors] || 'text-gray-600 bg-gray-100';
+ return colors[priority as keyof typeof colors] || 'text-gray-700 bg-gray-100 dark:bg-gray-700/40 dark:text-gray-300';
};
// Components
@@ -205,29 +205,30 @@ const StatCard: React.FC<{
icon: React.ReactNode;
change?: string;
changeType?: 'increase' | 'decrease';
- color: string;
-}> = ({ title, value, icon, change, changeType, color }) => (
+ colorClass: string;
+ iconColorClass: string;
+}> = ({ title, value, icon, change, changeType, colorClass, iconColorClass }) => (
-
+
- {title}
- {value}
+ {title}
+ {value}
{change && (
{changeType === 'increase' ? : }
{change}
)}
-
@@ -243,9 +244,9 @@ const ChartCard: React.FC<{
- {title}
+ {title}
{children}
);
@@ -258,10 +259,10 @@ const ListCard: React.FC<{
- {title}
+ {title}
{action}
{children}
@@ -320,14 +321,14 @@ const AuditDashboard: React.FC = () => {
if (overviewError) {
return (
-
+
-
- Error loading dashboard
- Please try refreshing the page
+
+ Error loading dashboard
+ Please try refreshing the page
@@ -337,25 +338,25 @@ const AuditDashboard: React.FC = () => {
}
return (
-
+
{/* Header */}
-
- Audit Dashboard
+
+ Audit Dashboard
@@ -363,14 +364,14 @@ const AuditDashboard: React.FC = () => {
@@ -396,9 +397,9 @@ const AuditDashboard: React.FC = () => {
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
className="inline-block"
>
-
+
- Loading dashboard...
+ Loading dashboard...
) : (
@@ -412,31 +413,35 @@ const AuditDashboard: React.FC = () => {
{/* Overview Stats */}
{overview && (
- Overview
+ Overview
}
- color="border-blue-500"
+ colorClass="border-primary"
+ iconColorClass="text-primary"
/>
}
- color="border-green-500"
+ colorClass="border-secondary"
+ iconColorClass="text-secondary"
/>
}
- color="border-purple-500"
+ colorClass="border-purple-500"
+ iconColorClass="text-purple-500 dark:text-purple-400"
/>
}
- color="border-red-500"
+ colorClass="border-destructive"
+ iconColorClass="text-destructive"
/>
@@ -453,12 +458,12 @@ const AuditDashboard: React.FC = () => {
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: index * 0.1 }}
- className="text-center p-4 rounded-lg bg-gray-50"
+ className="text-center p-4 rounded-lg bg-muted"
>
{item.status.replace('_', ' ')}
- {item.count}
+ {item.count}
))}
@@ -477,10 +482,10 @@ const AuditDashboard: React.FC = () => {
initial={{ x: -50, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ delay: index * 0.1 }}
- className="flex items-center justify-between p-3 rounded-lg bg-gray-50"
+ className="flex items-center justify-between p-3 rounded-lg bg-muted"
>
- {item.type.replace('_', ' ')}
-
+ {item.type.replace('_', ' ')}
+
{item.count}
@@ -500,7 +505,7 @@ const AuditDashboard: React.FC = () => {
View All
@@ -514,16 +519,16 @@ const AuditDashboard: React.FC = () => {
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: index * 0.1 }}
- className="flex items-center justify-between p-3 rounded-lg border border-gray-200 hover:bg-gray-50 transition-colors group"
+ className="flex items-center justify-between p-3 rounded-lg border border-border hover:bg-muted/50 transition-colors group"
>
-
+
{audit.name}
-
+
{audit.department.name} ⢠{audit.auditor.name}
-
+
{formatDate(audit.createdAt)}
@@ -531,7 +536,7 @@ const AuditDashboard: React.FC = () => {
{audit.status.replace('_', ' ')}
-
+ {/* */}
))}
@@ -549,7 +554,7 @@ const AuditDashboard: React.FC = () => {
View All
@@ -563,13 +568,13 @@ const AuditDashboard: React.FC = () => {
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: index * 0.1 }}
- className="p-4 rounded-lg border-l-4 border-red-500 bg-red-50 hover:bg-red-100 transition-colors group"
+ className="p-4 rounded-lg border-l-4 border-destructive bg-destructive/5 hover:bg-destructive/10 transition-colors group"
>
- {finding.title}
- {finding.audit.name}
-
+ {finding.title}
+ {finding.audit.name}
+
Assigned to: {finding.assignedTo.name}
@@ -577,12 +582,12 @@ const AuditDashboard: React.FC = () => {
{finding.priority}
-
+
{finding.actions.length > 0 && (
-
-
+
+
{finding.actions.length} action(s) ⢠Due: {formatDate(finding.actions[0].dueDate)}
@@ -604,7 +609,7 @@ const AuditDashboard: React.FC = () => {
Manage All
@@ -617,19 +622,19 @@ const AuditDashboard: React.FC = () => {
initial={{ x: -50, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ delay: index * 0.1 }}
- className="p-4 rounded-lg bg-yellow-50 border border-yellow-200 hover:bg-yellow-100 transition-colors"
+ className="p-4 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 hover:bg-yellow-100 dark:hover:bg-yellow-900/30 transition-colors"
>
- {action.title}
- {action.audit.name}
- Finding: {action.finding.title}
+ {action.title}
+ {action.audit.name}
+ Finding: {action.finding.title}
-
+
Overdue by {Math.ceil((new Date().getTime() - new Date(action.dueDate).getTime()) / (1000 * 60 * 60 * 24))} days
-
+
Assigned to: {action.assignedTo.name}
@@ -648,4 +653,4 @@ const AuditDashboard: React.FC = () => {
);
};
-export default AuditDashboard;
\ No newline at end of file
+export default AuditDashboard;
diff --git a/client/src/components/pages/Dashboard/batchdashboard.tsx b/client/src/components/pages/Dashboard/batchdashboard.tsx
index fc8bd69..1bab0f5 100644
--- a/client/src/components/pages/Dashboard/batchdashboard.tsx
+++ b/client/src/components/pages/Dashboard/batchdashboard.tsx
@@ -1,31 +1,21 @@
-import React, { useState } from 'react';
+import React from 'react';
import { useQuery } from '@tanstack/react-query';
-import { motion } from 'framer-motion';
import {
BarChart3,
- PieChart,
- TrendingUp,
Users,
- Layers,
+
Clipboard,
FileBox,
CheckCircle2,
XCircle,
Clock,
- ArrowUpCircle,
- CalendarRange,
- Download,
- BarChart4,
- CheckCheck,
- Droplets,
- Factory,
- Award
+ TrendingUp,
+ PieChart,
} from 'lucide-react';
import {
- Bar,
Line,
- Pie,
- Doughnut
+ Doughnut,
+ Bar,
} from 'react-chartjs-2';
import {
Chart as ChartJS,
@@ -40,7 +30,6 @@ import {
Legend,
Filler
} from 'chart.js';
-import { format } from 'date-fns';
import api, { API_ROUTES } from '../../../utils/api';
// Register ChartJS components
@@ -58,15 +47,45 @@ ChartJS.register(
);
const Dashboard: React.FC = () => {
- const [timeframe, setTimeframe] = useState<'weekly' | 'monthly' | 'quarterly'>('monthly');
- const [selectedMonth, setSelectedMonth] = useState(format(new Date(), 'MM'));
- const [selectedYear, setSelectedYear] = useState(format(new Date(), 'yyyy'));
+ // Helpers to resolve CSS variables to concrete colors for canvas rendering
+ const getCssVar = (name: string, fallback: string) => {
+ try {
+ if (typeof window === 'undefined') return fallback;
+ const val = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
+ return val || fallback;
+ } catch (e) {
+ return fallback;
+ }
+ };
+ const parseColorToRgba = (color: string, alpha: number) => {
+ if (!color) return `rgba(0,0,0,${alpha})`;
+ color = color.trim();
+ // already rgba
+ if (color.startsWith('rgba')) return color.replace(/rgba\(([^)]+)\)/, (_, vals) => `rgba(${vals.split(',').slice(0, 3).join(',')},${alpha})`);
+ // rgb -> rgba
+ if (color.startsWith('rgb(')) return color.replace('rgb(', 'rgba(').replace(')', `,${alpha})`);
+ // hex -> rgba
+ if (color.startsWith('#')) {
+ const hex = color.replace('#', '');
+ const bigint = parseInt(hex.length === 3 ? hex.split('').map(c => c + c).join('') : hex, 16);
+ const r = (bigint >> 16) & 255;
+ const g = (bigint >> 8) & 255;
+ const b = bigint & 255;
+ return `rgba(${r},${g},${b},${alpha})`;
+ }
+ // fallback: return color as-is (may be a named color) without alpha
+ return color;
+ };
+
+ const successColor = getCssVar('--success', '#16a34a');
+ const secondaryColor = getCssVar('--secondary', '#3b82f6');
+ const destructiveColor = getCssVar('--destructive', '#ef4444');
// Fetch overview statistics
- const {
- data: overviewData,
- isLoading: overviewLoading,
- error: overviewError
+ const {
+ data: overviewData,
+ isLoading: overviewLoading,
+ error: overviewError
} = useQuery({
queryKey: ['dashboardOverview'],
queryFn: async () => {
@@ -81,15 +100,14 @@ const Dashboard: React.FC = () => {
});
// Fetch batch trends
- const {
- data: trendData,
- isLoading: trendLoading,
- error: trendError
+ const {
+ data: trendData,
+ isLoading: trendLoading,
} = useQuery({
- queryKey: ['batchTrends', timeframe],
+ queryKey: ['batchTrends', 'monthly'],
queryFn: async () => {
const res = await api.get(API_ROUTES.DASHBOARD.BATCH_TRENDS, {
- params: { period: timeframe },
+ params: { period: 'monthly' },
headers: {
'Authorization': `Bearer ${localStorage.getItem('authToken')}`
}
@@ -100,10 +118,9 @@ const Dashboard: React.FC = () => {
});
// Fetch product performance
- const {
- data: productData,
- isLoading: productLoading,
- error: productError
+ const {
+ data: productData,
+ isLoading: productLoading,
} = useQuery({
queryKey: ['productPerformance'],
queryFn: async () => {
@@ -117,1215 +134,301 @@ const Dashboard: React.FC = () => {
staleTime: 5 * 60 * 1000
});
- // Fetch user activity
- const {
- data: userData,
- isLoading: userLoading,
- error: userError
- } = useQuery({
- queryKey: ['userActivity'],
- queryFn: async () => {
- const res = await api.get(API_ROUTES.DASHBOARD.USER_ACTIVITY, {
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
- }
- });
- return res.data.users;
- },
- staleTime: 5 * 60 * 1000
- });
-
- // Fetch quality metrics
- const {
- data: qualityData,
- isLoading: qualityLoading,
- error: qualityError
- } = useQuery({
- queryKey: ['qualityMetrics'],
- queryFn: async () => {
- const res = await api.get(API_ROUTES.DASHBOARD.QUALITY_METRICS, {
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
- }
- });
- return res.data.qualityMetrics;
- },
- staleTime: 5 * 60 * 1000
- });
-
- // Fetch monthly batch summary
- const {
- data: monthlySummaryData,
- isLoading: monthlySummaryLoading,
- error: monthlySummaryError
- } = useQuery({
- queryKey: ['monthlySummary', selectedMonth, selectedYear],
- queryFn: async () => {
- const res = await api.get(API_ROUTES.DASHBOARD.MONTHLY_SUMMARY, {
- params: { month: selectedMonth, year: selectedYear },
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
- }
- });
- return res.data.summary;
- },
- staleTime: 5 * 60 * 1000
- });
-
- // Fetch standard usage
- const {
- data: standardData,
- isLoading: standardLoading,
- error: standardError
- } = useQuery({
- queryKey: ['standardUsage'],
- queryFn: async () => {
- const res = await api.get(API_ROUTES.DASHBOARD.STANDARD_USAGE, {
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
- }
- });
- return res.data.standards;
- },
- staleTime: 5 * 60 * 1000
- });
-
- // Prepare chart data based on API responses
+ // Prepare chart data
const batchTrendChartData = React.useMemo(() => {
if (!trendData?.trends) return null;
-
+
return {
- labels: trendData.trends.map((item: any) => item.date),
+ labels: trendData.trends.slice(-6).map((item: any) => item.date),
datasets: [
{
label: 'Approved',
- data: trendData.trends.map((item: any) => item.approved),
- borderColor: 'rgba(34, 197, 94, 1)',
- backgroundColor: 'rgba(34, 197, 94, 0.5)',
+ data: trendData.trends.slice(-6).map((item: any) => item.approved),
+ borderColor: successColor,
+ backgroundColor: parseColorToRgba(successColor, 0.12),
tension: 0.3,
},
{
- label: 'Submitted',
- data: trendData.trends.map((item: any) => item.submitted),
- borderColor: 'rgba(59, 130, 246, 1)',
- backgroundColor: 'rgba(59, 130, 246, 0.5)',
- tension: 0.3,
- },
- {
- label: 'Rejected',
- data: trendData.trends.map((item: any) => item.rejected),
- borderColor: 'rgba(239, 68, 68, 1)',
- backgroundColor: 'rgba(239, 68, 68, 0.5)',
- tension: 0.3,
- },
- {
- label: 'Draft',
- data: trendData.trends.map((item: any) => item.draft),
- borderColor: 'rgba(161, 161, 170, 1)',
- backgroundColor: 'rgba(161, 161, 170, 0.5)',
+ label: 'Pending',
+ data: trendData.trends.slice(-6).map((item: any) => item.submitted + item.draft),
+ borderColor: secondaryColor,
+ backgroundColor: parseColorToRgba(secondaryColor, 0.12),
tension: 0.3,
}
]
};
}, [trendData]);
- // Prepare product performance chart data
- const productPerformanceChartData = React.useMemo(() => {
- if (!productData || productData.length === 0) return null;
-
- // Sort products by total batches
- const topProducts = [...productData].sort((a, b) => b.totalBatches - a.totalBatches).slice(0, 5);
-
- return {
- labels: topProducts.map(product => product.name),
- datasets: [
- {
- label: 'Approved',
- data: topProducts.map(product => product.approvedBatches),
- backgroundColor: 'rgba(34, 197, 94, 0.8)',
- },
- {
- label: 'Rejected',
- data: topProducts.map(product => product.rejectedBatches),
- backgroundColor: 'rgba(239, 68, 68, 0.8)',
- },
- {
- label: 'Pending',
- data: topProducts.map(product => product.pendingBatches),
- backgroundColor: 'rgba(59, 130, 246, 0.8)',
- }
- ]
- };
- }, [productData]);
-
- // Prepare quality metrics chart data
- const qualityMetricsChartData = React.useMemo(() => {
- if (!qualityData || qualityData.length === 0) return null;
-
- // Take top 5 products by batch count
- const topProducts = [...qualityData].sort((a, b) => b.totalBatches - a.totalBatches).slice(0, 5);
-
- return {
- labels: topProducts.map(product => product.productName),
- datasets: [
- {
- label: 'Moisture (%)',
- data: topProducts.map(product => product.avgMoisture),
- backgroundColor: 'rgba(147, 51, 234, 0.7)',
- borderColor: 'rgba(147, 51, 234, 1)',
- borderWidth: 1,
- borderRadius: 5,
- },
- {
- label: 'Total Ash (%)',
- data: topProducts.map(product => product.avgTotalAsh),
- backgroundColor: 'rgba(249, 115, 22, 0.7)',
- borderColor: 'rgba(249, 115, 22, 1)',
- borderWidth: 1,
- borderRadius: 5,
- }
- ]
- };
- }, [qualityData]);
-
- // Prepare water activity chart (separate from other metrics due to scale difference)
- const waterActivityChartData = React.useMemo(() => {
- if (!qualityData || qualityData.length === 0) return null;
-
- // Take top 5 products by batch count
- const topProducts = [...qualityData].sort((a, b) => b.totalBatches - a.totalBatches).slice(0, 5);
-
- return {
- labels: topProducts.map(product => product.productName),
- datasets: [
- {
- label: 'Water Activity',
- data: topProducts.map(product => product.avgWaterActivity),
- backgroundColor: 'rgba(6, 182, 212, 0.7)',
- borderColor: 'rgba(6, 182, 212, 1)',
- borderWidth: 1,
- borderRadius: 5,
- }
- ]
- };
- }, [qualityData]);
-
-
-
- // Prepare status distribution doughnut chart
const statusDistributionChartData = React.useMemo(() => {
if (!overviewData) return null;
-
+
return {
- labels: ['Approved', 'Pending', 'Rejected', 'Draft'],
+ labels: ['Approved', 'Pending', 'Rejected'],
datasets: [
{
data: [
overviewData.batches.approved,
overviewData.batches.pending,
- overviewData.batches.rejected,
- overviewData.batches.total - (overviewData.batches.approved + overviewData.batches.pending + overviewData.batches.rejected)
+ overviewData.batches.rejected
],
backgroundColor: [
- 'rgba(34, 197, 94, 0.8)',
- 'rgba(59, 130, 246, 0.8)',
- 'rgba(239, 68, 68, 0.8)',
- 'rgba(161, 161, 170, 0.8)'
- ],
- borderColor: [
- 'rgba(34, 197, 94, 1)',
- 'rgba(59, 130, 246, 1)',
- 'rgba(239, 68, 68, 1)',
- 'rgba(161, 161, 170, 1)'
+ parseColorToRgba(successColor, 0.85),
+ parseColorToRgba(secondaryColor, 0.85),
+ parseColorToRgba(destructiveColor, 0.85),
],
+ borderColor: [successColor, secondaryColor, destructiveColor],
borderWidth: 1,
}
]
};
}, [overviewData]);
-
- // Standard usage chart
- const standardUsageChartData = React.useMemo(() => {
- if (!standardData || standardData.length === 0) return null;
-
- // Take top 7 standards by usage
- const topStandards = [...standardData].sort((a, b) => b.usageCount - a.usageCount).slice(0, 7);
-
+
+ const productPerformanceChartData = React.useMemo(() => {
+ if (!productData || productData.length === 0) return null;
+
+ const topProducts = [...productData].sort((a, b) => b.totalBatches - a.totalBatches).slice(0, 5);
+
return {
- labels: topStandards.map(std => std.name),
+ labels: topProducts.map(product => product.name.length > 15 ? product.name.substring(0, 15) + '...' : product.name),
datasets: [
{
- data: topStandards.map(std => std.usageCount),
- backgroundColor: [
- 'rgba(59, 130, 246, 0.7)',
- 'rgba(147, 51, 234, 0.7)',
- 'rgba(249, 115, 22, 0.7)',
- 'rgba(16, 185, 129, 0.7)',
- 'rgba(239, 68, 68, 0.7)',
- 'rgba(245, 158, 11, 0.7)',
- 'rgba(6, 182, 212, 0.7)'
- ],
- borderWidth: 0,
- hoverOffset: 10
+ label: 'Approved',
+ data: topProducts.map(product => product.approvedBatches),
+ backgroundColor: 'rgba(23, 142, 200, 0.8)',
+ borderColor: 'rgba(23, 142, 200, 1)',
+ borderWidth: 1,
+ borderRadius: 4,
}
]
};
- }, [standardData]);
-
- // Page animation variants
- const containerVariants = {
- hidden: { opacity: 0 },
- visible: {
- opacity: 1,
- transition: {
- staggerChildren: 0.1
- }
- }
- };
-
- const itemVariants = {
- hidden: { y: 20, opacity: 0 },
- visible: {
- y: 0,
- opacity: 1,
- transition: {
- type: "spring",
- damping: 15
- }
- }
- };
-
- // Generate month options for dropdown
- const monthOptions = Array.from({ length: 12 }, (_, i) => ({
- value: String(i + 1).padStart(2, '0'),
- label: format(new Date(2000, i, 1), 'MMMM')
- }));
-
- // Generate year options for dropdown
- const currentYear = new Date().getFullYear();
- const yearOptions = Array.from({ length: 5 }, (_, i) => ({
- value: String(currentYear - i),
- label: String(currentYear - i)
- }));
+ }, [productData]);
- return (
-
- {/* Dashboard Header */}
-
-
- Batch Dashboard
- Overview of batch processing performance and metrics
+ if (overviewLoading) {
+ return (
+
+
-
-
- {/* Time frame selector */}
-
-
-
-
-
-
- {/* Month and Year selector */}
-
-
-
-
-
-
-
+
+ );
+ }
+
+ if (overviewError) {
+ return (
+
+
+
+ Failed to load dashboard data
-
-
- {/* Top Stats Cards */}
-
- {/* Total Batches */}
-
-
-
-
-
-
- TOTAL BATCHES
-
-
- {overviewLoading ? "..." : overviewData?.batches?.total || 0}
-
-
-
- {trendData?.trends && trendData.trends.length > 1
- ? `${Math.round(((trendData.trends[trendData.trends.length-1].approved +
- trendData.trends[trendData.trends.length-1].submitted +
- trendData.trends[trendData.trends.length-1].rejected +
- trendData.trends[trendData.trends.length-1].draft) /
- (trendData.trends[trendData.trends.length-2].approved +
- trendData.trends[trendData.trends.length-2].submitted +
- trendData.trends[trendData.trends.length-2].rejected +
- trendData.trends[trendData.trends.length-2].draft || 1) - 1) * 100)}%`
- : "0%"
- }
-
-
-
-
-
-
-
- Approved: {overviewData?.batches?.approved || 0}
-
-
-
- Pending: {overviewData?.batches?.pending || 0}
-
-
-
- Rejected: {overviewData?.batches?.rejected || 0}
-
-
-
-
- {/* Products */}
-
-
-
-
-
-
- PRODUCTS
-
-
- {overviewLoading ? "..." : overviewData?.products || 0}
-
- {productData && (
-
- {productData.length} active
-
- )}
-
-
-
-
-
+
+ );
+ }
+
+ const stats = [
+ {
+ title: 'Total Batches',
+ value: overviewData?.batches?.total || 0,
+ icon: BarChart3,
+ iconColorVar: 'var(--primary)',
+ },
+ {
+ title: 'Approved',
+ value: overviewData?.batches?.approved || 0,
+ icon: CheckCircle2,
+ iconColorVar: 'var(--success, #16a34a)',
+ },
+ {
+ title: 'Pending',
+ value: overviewData?.batches?.pending || 0,
+ icon: Clock,
+ iconColorVar: 'var(--secondary)',
+ },
+ {
+ title: 'Products',
+ value: overviewData?.products || 0,
+ icon: FileBox,
+ iconColorVar: 'var(--primary)',
+ },
+ {
+ title: 'Users',
+ value: overviewData?.users || 0,
+ icon: Users,
+ iconColorVar: 'var(--secondary)',
+ },
+ {
+ title: 'Standards',
+ value: overviewData?.standards || 0,
+ icon: Clipboard,
+ iconColorVar: 'var(--primary)',
+ },
+ ];
- {/* Standards */}
-
-
-
-
-
-
- STANDARDS
-
-
- {overviewLoading ? "..." : overviewData?.standards || 0}
-
- {standardData && (
-
- {standardData.filter((s: { status: string; }) => s.status === 'ACTIVE').length} active
-
- )}
-
-
-
-
-
-
- Top: {standardData && standardData.length > 0 ? standardData[0].name : 'N/A'}
-
- {standardData && standardData.length > 0 ? `${standardData[0].usageCount} uses` : ''}
-
-
+ return (
+
+
+ {/* Header */}
+
+ Dashboard
+ Batch processing overview
+
- {/* Users */}
-
-
-
-
-
-
- USERS
-
-
- {overviewLoading ? "..." : overviewData?.users || 0}
-
- {userData && (
-
- {userData.filter((u: { totalActivities: number; }) => u.totalActivities > 0).length} active
-
- )}
+ {/* Stats Grid */}
+
+ {stats.map((stat, index) => (
+
+
+
+ {stat.title}
+
+ {stat.value.toLocaleString()}
+
+
+
+
+
-
-
-
-
- Most active: {userData && userData.length > 0 ? userData[0].name : 'N/A'}
-
- {userData && userData.length > 0 ? `${userData[0].totalActivities} actions` : ''}
-
-
-
+ ))}
+
-
- {/* Batch Trends Chart */}
-
-
-
-
-
- Batch Processing Trends
-
-
- {timeframe === 'weekly' ? 'Last 7 days' :
- timeframe === 'monthly' ? 'Last 30 days' : 'Last 3 months'}
-
+ {/* Charts Section */}
+
+ {/* Batch Trends */}
+
-
{trendLoading ? (
- ) : trendError ? (
-
- Failed to load trend data
+
- ) : !batchTrendChartData ? (
-
- No trend data available
-
- ) : (
+ ) : batchTrendChartData ? (
-
+ ) : (
+
+ No trend data available
+
)}
-
- {/* Status Distribution */}
-
-
-
-
-
- Batch Status Distribution
-
+ {/* Status Distribution */}
+
-
- {overviewLoading ? (
-
- ) : overviewError ? (
-
- Failed to load overview data
-
- ) : !statusDistributionChartData ? (
-
- No status data available
-
- ) : (
+ {statusDistributionChartData ? (
-
-
+
- {overviewData && (
-
- {overviewData.batches.total}
- Total Batches
-
- )}
-
-
- )}
-
-
-
-
-
- {/* Product Performance */}
-
-
-
-
-
- Product Performance
-
-
-
-
-
- {productLoading ? (
-
- ) : productError ? (
-
- Failed to load product data
-
- ) : !productPerformanceChartData ? (
-
- No product data available
-
- ) : (
-
- ({
- ...ds,
- barThickness: 20
- }))
- }}
- options={{
- responsive: true,
- maintainAspectRatio: false,
- scales: {
- x: {
- stacked: true,
- grid: {
- display: false
- }
- },
- y: {
- stacked: true,
- beginAtZero: true,
- grid: {
- color: 'rgba(0, 0, 0, 0.05)',
- }
- }
- },
- plugins: {
- legend: {
- position: 'top' as const,
- labels: {
- boxWidth: 12,
- usePointStyle: true,
- pointStyle: 'circle'
- }
- },
- tooltip: {
- callbacks: {
- footer: (tooltipItems) => {
- // Add approval rate to the tooltip
- if (tooltipItems.length > 0) {
-
- const label = tooltipItems[0].label;
- const matchingProduct = productData?.find((p: { name: string; }) => p.name === label);
- if (matchingProduct) {
- return `Approval Rate: ${matchingProduct.approvalRate}%`;
- }
- }
- return '';
- }
- }
- }
- }
- }}
- />
-
- )}
-
-
-
- {/* Quality Metrics */}
-
-
-
-
-
- Quality Metrics
-
-
-
-
-
- {qualityLoading ? (
-
- ) : qualityError ? (
-
- Failed to load quality data
-
- ) : !qualityMetricsChartData ? (
-
- No quality metrics available
-
) : (
-
- ({
- ...ds,
- barThickness: 20
- }))
- }}
- options={{
- responsive: true,
- maintainAspectRatio: false,
- scales: {
- x: {
- grid: {
- display: false
- }
- },
- y: {
- beginAtZero: true,
- grid: {
- color: 'rgba(0, 0, 0, 0.05)',
- },
- title: {
- display: true,
- text: 'Percentage (%)'
- }
- }
- },
- plugins: {
- legend: {
- position: 'top' as const,
- align: 'end' as const,
- labels: {
- boxWidth: 12,
- usePointStyle: true,
- pointStyle: 'circle'
- }
- },
- tooltip: {
- callbacks: {
- label: function(context) {
- return context.dataset.label + ': ' + context.parsed.y + '%';
- }
- }
- }
- }
- }}
- />
+
+ No status data available
)}
-
-
+
-
- {/* Water Activity Chart */}
-
-
-
-
-
- Water Activity
-
-
+ {/* Product Performance */}
+
+
+
+ Top Products Performance
-
- {qualityLoading ? (
-
- ) : qualityError ? (
-
- Failed to load quality data
-
- ) : !waterActivityChartData ? (
-
- No water activity data available
-
- ) : (
-
- ({
- ...ds,
- barThickness: 16
- }))
- }}
- options={{
- responsive: true,
- maintainAspectRatio: false,
- indexAxis: 'y' as const,
- scales: {
- x: {
- beginAtZero: true,
- grid: {
- color: 'rgba(0, 0, 0, 0.05)',
- },
- title: {
- display: true,
- text: 'Water Activity (Aw)'
- }
- },
- y: {
- grid: {
- display: false
- }
- }
+ {productLoading ? (
+
+ ) : productPerformanceChartData ? (
+
+
-
- )}
-
-
-
- {/* Standard Usage */}
-
-
-
-
-
- Standard Usage
-
-
-
-
- {standardLoading ? (
-
- ) : standardError ? (
-
- Failed to load standard data
-
- ) : !standardUsageChartData ? (
-
- No standard data available
-
- ) : (
-
- )}
-
-
-
- {/* Monthly Summary */}
-
-
-
-
-
- Monthly Summary
-
-
- {monthlySummaryData?.month || format(new Date(), 'MMMM yyyy')}
-
-
-
-
- {monthlySummaryLoading ? (
-
- ) : monthlySummaryError ? (
-
- Failed to load monthly data
-
- ) : !monthlySummaryData ? (
-
- No monthly data available
-
- ) : (
-
-
-
- Total Batches
- {monthlySummaryData.totalBatches}
-
-
- Approval Time
- {monthlySummaryData.timeToApproval}h
- average
-
-
-
-
-
-
-
- Approved
-
-
- {monthlySummaryData.approved}
-
- ({Math.round((monthlySummaryData.approved / monthlySummaryData.totalBatches || 0) * 100)}%)
-
-
-
-
-
-
-
- Pending
-
-
- {monthlySummaryData.pending}
-
- ({Math.round((monthlySummaryData.pending / monthlySummaryData.totalBatches || 0) * 100)}%)
-
-
-
-
-
-
-
- Rejected
-
-
- {monthlySummaryData.rejected}
-
- ({Math.round((monthlySummaryData.rejected / monthlySummaryData.totalBatches || 0) * 100)}%)
-
-
-
-
-
-
- Top Products
-
- {monthlySummaryData.productDistribution &&
- (Object.entries(monthlySummaryData.productDistribution) as [string, number][])
- .sort((a, b) => b[1] - a[1])
- .slice(0, 2)
- .map(([name, count], idx) => (
-
- {name}
- {count}
-
- ))
- }
-
-
-
- )}
-
-
-
-
- {/* User Activity */}
-
-
-
-
-
- User Activity
-
-
-
-
-
- {userLoading ? (
-
- ) : userError ? (
-
- Failed to load user data
-
- ) : !userData || userData.length === 0 ? (
-
- No user activity data available
+ x: { grid: { display: false } }
+ },
+ plugins: {
+ legend: { display: false }
+ }
+ }}
+ />
) : (
-
-
-
-
- |
- User
- |
-
- Role
- |
-
- Batches Created
- |
-
- Batches Reviewed
- |
-
- Total Activities
- |
-
-
-
- {userData.slice(0, 5).map((user: { id: React.Key | null | undefined; name: string | number | bigint | boolean | React.ReactElement> | Iterable | Promise> | Iterable | null | undefined> | null | undefined; email: string | number | bigint | boolean | React.ReactElement> | Iterable | React.ReactPortal | Promise> | Iterable | null | undefined> | null | undefined; role: string | number | bigint | boolean | React.ReactElement> | Iterable | React.ReactPortal | Promise> | Iterable | null | undefined> | null | undefined; batchesCreated: string | number | bigint | boolean | React.ReactElement> | Iterable | React.ReactPortal | Promise> | Iterable | null | undefined> | null | undefined; batchesReviewed: string | number | bigint | boolean | React.ReactElement> | Iterable | React.ReactPortal | Promise> | Iterable | null | undefined> | null | undefined; totalActivities: string | number | bigint | boolean | React.ReactElement> | Iterable | Promise> | Iterable | null | undefined> | null | undefined; }) => (
-
-
-
-
- {(user.name?.toString().charAt(0).toUpperCase() ?? '')}
-
-
- {user.name}
- {user.email}
-
-
- |
-
-
- {user.role}
-
- |
-
- {user.batchesCreated}
- |
-
- {user.batchesReviewed}
- |
-
- {user.totalActivities}
-
- |
-
- ))}
-
-
+
+ No product data available
)}
-
-
- {/* Footer Stats */}
-
-
-
-
-
-
-
- {overviewLoading ? "..." :
- overviewData?.batches?.total ?
- Math.round((overviewData.batches.approved / overviewData.batches.total) * 100) : 0}%
-
- Approval Rate
-
-
-
-
-
-
-
- {monthlySummaryData?.timeToApproval || "N/A"}
-
- Avg. Processing Time (hours)
-
-
-
-
-
-
-
- {productData?.length ?
- productData.reduce((sum: any, p: { totalBatches: any; }) => sum + p.totalBatches, 0) / productData.length : 0}
-
- Avg. Batches per Product
-
-
-
-
-
-
-
- {qualityData?.length || 0}
+
+ {/* Summary Section */}
+
+ Quick Summary
+
+
+ Approval Rate:
+
+ {overviewData?.batches?.total ?
+ Math.round((overviewData.batches.approved / overviewData.batches.total) * 100) : 0}%
+
+
+
+ Active Products:
+ {overviewData?.products || 0}
+
+
+ Total Standards:
+ {overviewData?.standards || 0}
- Products Monitored
-
-
+
+
);
};
-export default Dashboard;
\ No newline at end of file
+export default Dashboard;
diff --git a/client/src/components/pages/Dashboard/rawDashboard.tsx b/client/src/components/pages/Dashboard/rawDashboard.tsx
index e06e728..e6292d3 100644
--- a/client/src/components/pages/Dashboard/rawDashboard.tsx
+++ b/client/src/components/pages/Dashboard/rawDashboard.tsx
@@ -4,18 +4,9 @@ import type React from 'react';
import { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import {
- Package,
- Truck,
- Search,
- Settings,
AlertTriangle,
- Recycle,
- TrendingUp,
Loader2,
- Warehouse,
Factory,
- ChevronDown,
- ChevronUp,
} from 'lucide-react';
import api, { API_ROUTES } from '../../../utils/api';
import { Bar, Doughnut } from 'react-chartjs-2';
@@ -60,7 +51,6 @@ interface WasteStock {
const RawDashboard: React.FC = () => {
const [totalStock, setTotalStock] = useState (0);
- const [totalStockDetails, setTotalStockDetails] = useState([]);
const [pendingPOs, setPendingPOs] = useState(0);
const [pendingPODetails, setPendingPODetails] = useState([]);
const [stockUnderCleaning, setStockUnderCleaning] = useState(0);
@@ -74,10 +64,7 @@ const RawDashboard: React.FC = () => {
total: 0,
});
const [loading, setLoading] = useState(true);
- const [totalVendors, setTotalVendors] = useState(0);
- const [totalPurchaseOrders, setTotalPurchaseOrders] = useState(0);
const [productWiseWaste, setProductWiseWaste] = useState([]);
- //const [productWiseWaste, setProductWiseWaste] = useState({ afterCleaning: [], afterProcessing: [] })
const [stockDistribution, setStockDistribution] = useState([]);
const [productWiseConversion, setProductWiseConversion] = useState([]);
@@ -94,8 +81,6 @@ const RawDashboard: React.FC = () => {
api.get(API_ROUTES.RAW.GET_STOCK_IN_PROCESSING, { headers }),
api.get(API_ROUTES.RAW.GET_LOW_STOCK_ALERTS, { headers }),
api.get(API_ROUTES.RAW.GET_WASTE_STOCK, { headers }),
- api.get(API_ROUTES.RAW.GET_TOTAL_VENDORS, { headers }),
- api.get(API_ROUTES.RAW.GET_TOTAL_PURCHASE_ORDERS, { headers }),
api.get(API_ROUTES.RAW.GET_PRODUCT_WISE_WASTE, { headers }),
api.get(API_ROUTES.RAW.GET_STOCK_DISTRIBUTION, { headers }),
api.get(API_ROUTES.RAW.GET_PRODUCT_WISE_CONVERSION, { headers }),
@@ -108,15 +93,12 @@ const RawDashboard: React.FC = () => {
processingRes,
lowStockRes,
wasteRes,
- vendorsRes,
- poRes,
productWiseWasteRes,
stockDistributionRes,
productWiseConversionRes,
] = responses;
setTotalStock(totalStockRes.data.totalRawMaterialStock || 0);
- setTotalStockDetails(totalStockRes.data.details || []);
setPendingPOs(pendingPOsRes.data.pendingPOs || 0);
setPendingPODetails(pendingPOsRes.data.details || []);
setStockUnderCleaning(cleaningRes.data.stockUnderCleaning || 0);
@@ -131,8 +113,6 @@ const RawDashboard: React.FC = () => {
total: 0,
}
);
- setTotalVendors(vendorsRes.data.totalVendors || 0);
- setTotalPurchaseOrders(poRes.data.totalPurchaseOrders || 0);
// ...existing code...
setProductWiseWaste(
productWiseWasteRes.data.productWiseWasteStock || []
@@ -198,67 +178,6 @@ const RawDashboard: React.FC = () => {
const normalizedProductList = Object.values(normalizedProductWiseWaste);
- // Normalize Total Raw Material Stock details
- const normalizedTotalStockDetails = totalStockDetails.reduce((acc: any, item: any) => {
- const normalizedName = item.rawMaterial?.name?.toUpperCase() || 'UNKNOWN';
- const key = `${normalizedName}-${item.warehouse?.name || 'UNKNOWN'}`;
-
- if (!acc[key]) {
- acc[key] = {
- rawMaterial: {
- name: normalizedName,
- skuCode: item.rawMaterial?.skuCode || '',
- },
- warehouse: item.warehouse,
- currentQuantity: (item.currentQuantity || 0),
- };
- } else {
- acc[key].currentQuantity += (item.currentQuantity || 0);
- }
- return acc;
- }, {});
-
- const normalizedTotalStockDetailsList = Object.values(normalizedTotalStockDetails);
-
- // Create product-wise aggregation for pie chart
- const productQuantityMap = normalizedTotalStockDetailsList.reduce((acc: Record, item: any) => {
- const productName = item.rawMaterial?.name || 'UNKNOWN';
- if (!acc[productName]) {
- acc[productName] = 0;
- }
- acc[productName] += item.currentQuantity;
- return acc;
- }, {});
-
- const productLabels = Object.keys(productQuantityMap);
- const productQuantities = Object.values(productQuantityMap);
-
- const productPieData = {
- labels: productLabels,
- datasets: [
- {
- label: 'Quantity by Product',
- data: productQuantities,
- backgroundColor: [
- '#3B82F6',
- '#10B981',
- '#F59E0B',
- '#EF4444',
- '#8B5CF6',
- '#06B6D4',
- '#84CC16',
- '#F97316',
- '#EC4899',
- '#14B8A6',
- '#F43F5E',
- '#6366F1',
- ],
- borderWidth: 2,
- borderColor: '#ffffff',
- },
- ],
- };
-
const warehouseLabels = stockDistribution.map(
(w) => w.warehouse?.name || 'N/A'
);
@@ -331,237 +250,134 @@ const RawDashboard: React.FC = () => {
}
return (
-
- {/* Enhanced Header */}
-
-
-
-
-
-
-
-
-
-
- Raw Material Dashboard
-
-
- Real-time inventory monitoring and analytics
-
-
-
-
+
+ {/* Compact Header */}
+
+
+
+
+
+
+ Raw Material Dashboard
+ Real-time inventory overview
-
+
-
- {/* Enhanced Stats Grid - Better 4-column layout */}
+
+ {/* Key Metrics - Compact Table Style */}
-
- }
- label="Total Raw Material Stock"
- value={totalStock}
- unit="kg/litre"
- color="blue"
- details={normalizedTotalStockDetailsList}
- detailsFormatter={(details) =>
- details.map(
- (s: any) =>
- `${s.rawMaterial?.name || ''} (${s.rawMaterial?.skuCode || ''}): ${s.currentQuantity} in ${s.warehouse?.name || ''}`
- )
- }
- large={true}
- />
-
-
- {/* Product-wise Quantity Pie Chart */}
-
-
-
-
-
- Product-wise Stock Distribution
-
- By product quantity
-
-
-
-
-
-
-
- }
- label="POs Pending Delivery"
- value={pendingPOs}
- unit="orders"
- color="orange"
- details={pendingPODetails}
- detailsFormatter={(details) =>
- details.map(
- (po) =>
- `PO#${po.id} (${po.vendor?.name || ''}): ${po.items?.length || 0} items`
- )
- }
- />
- }
- label="Waste Stock"
- value={wasteStock.total}
- unit="kg/litre"
- color="red"
- tooltip={`After Cleaning: ${wasteStock.afterCleaning.total}, After Processing: ${wasteStock.afterProcessing.total}`}
- />
- }
- label="Under Cleaning"
- value={stockUnderCleaning}
- unit="kg/litre"
- color="purple"
- details={cleaningDetails}
- detailsFormatter={(details) =>
- details.map(
- (c) =>
- `${c.rawMaterial?.name || ''}: ${c.quantity} (${c.status})`
- )
- }
- />
- }
- label="In Processing"
- value={stockInProcessing}
- unit="kg/litre"
- color="green"
- details={processingDetails}
- detailsFormatter={(details) =>
- details.map(
- (p) =>
- `${p.inputRawMaterial?.name || ''}: ${p.quantityInput} (${p.status})`
- )
- }
- />
- }
- label="Total Vendors"
- value={totalVendors}
- unit="vendors"
- color="indigo"
- />
- }
- label="Purchase Orders"
- value={totalPurchaseOrders}
- unit="orders"
- color="teal"
- />
+
+
+ {/* Row 1 - Main Stock */}
+
+ | Total Raw Material |
+
+
+ {totalStock.toLocaleString()}
+ kg
+
+ |
+
+
+ {/* Row 2 - Pending POs */}
+
+ | POs Pending |
+
+
+ {pendingPOs}
+ orders
+
+ |
+ {pendingPODetails.length} details |
+
+
+ {/* Row 3 - Under Cleaning */}
+
+ | Under Cleaning |
+
+
+ {stockUnderCleaning.toLocaleString()}
+ kg
+
+ |
+ {cleaningDetails.length} items |
+
+
+ {/* Row 4 - In Processing */}
+
+ | In Processing |
+
+
+ {stockInProcessing.toLocaleString()}
+ kg
+
+ |
+ {processingDetails.length} items |
+
+
+ {/* Row 5 - Waste Stock */}
+
+ | Waste Stock |
+
+
+ {wasteStock.total.toLocaleString()}
+ kg
+
+ |
+
+ Cleaning: {wasteStock.afterCleaning.total} | Processing: {wasteStock.afterProcessing.total}
+ |
+
+
+
- {/* Charts Section - Better Layout */}
-
- {/* Product-wise Waste Chart - Takes 2 columns */}
+ {/* Charts Section - Compact */}
+
+ {/* Product-wise Waste Table */}
-
-
-
-
-
-
- Product-wise Waste Table
-
-
- Summary of raw material, cleaning, waste, and processing
-
-
+
+ Product-wise Summary
-
+
-
- |
- Metric
- |
- {normalizedProductList.map((p: any) => (
-
- {p.productName}
+ |
+ | Metric |
+ {normalizedProductList.slice(0, 4).map((p: any) => (
+
+ {p.productName.substring(0, 10)}
|
))}
{[
- { label: 'Raw material', key: 'rawMaterial' },
- { label: 'Cleaning', key: 'cleaning' },
- {
- label: 'Waste after cleaning',
- key: 'wasteAfterCleaning',
- },
+ { label: 'Raw', key: 'rawMaterial' },
{ label: 'Cleaned', key: 'cleaned' },
- { label: 'Processing', key: 'processing' },
- {
- label: 'Waste after processing',
- key: 'wasteAfterProcessing',
- },
{ label: 'Processed', key: 'processed' },
].map((row) => (
-
- |
- {row.label}
- |
- {normalizedProductList.map((p: any) => (
-
+ |
+ | {row.label} |
+ {normalizedProductList.slice(0, 4).map((p: any) => (
+
{p[row.key]}
|
))}
@@ -572,26 +388,18 @@ const RawDashboard: React.FC = () => {
- {/* Stock Distribution Pie Chart - Takes 1 column */}
+ {/* Stock Distribution Pie */}
-
-
-
-
-
-
- Stock Distribution
-
- By warehouse
-
-
-
+ Stock Distribution
+
{
plugins: {
legend: {
position: 'bottom',
- labels: {
- usePointStyle: true,
- padding: 15,
- font: { size: 11 },
- },
+ labels: { font: { size: 10 }, padding: 10 },
},
},
cutout: '60%',
@@ -614,403 +418,60 @@ const RawDashboard: React.FC = () => {
- {/* Conversion Ratio Chart - Full Width */}
+ {/* Conversion Bar Chart */}
-
-
-
-
-
-
- Product-wise Conversion Efficiency
-
-
- Conversion percentage by product SKU
-
-
-
-
+ Conversion Efficiency (%)
+
value + '%',
- },
- },
+ x: { grid: { display: false }, ticks: { font: { size: 10 } } },
+ y: { beginAtZero: true, max: 100, ticks: { callback: (v) => v + '%', font: { size: 10 } } },
},
}}
/>
- {/* Alerts and Details Section */}
-
- {/* Low Stock Alerts */}
-
-
-
-
-
-
-
- Low Stock Alerts
-
-
- Items below minimum reorder level
-
-
-
- {lowStockAlerts.length > 0 && (
-
- {lowStockAlerts.length} alerts
-
- )}
-
-
-
- {lowStockAlerts.length === 0 ? (
-
-
-
- All stock levels are healthy!
-
-
- No items below minimum reorder levels
-
-
- ) : (
-
- {lowStockAlerts.map((alert, index) => (
-
-
-
-
- {alert.name}
-
-
- {alert.skuCode}
-
-
-
- Available
-
- {alert.available}
-
-
-
-
-
- Min Level: {alert.minReorderLevel}
-
-
- {alert.minReorderLevel - alert.available} units short
-
-
-
- ))}
-
- )}
-
-
-
- {/* Waste Stock Details */}
+ {/* Low Stock Alerts */}
+ {lowStockAlerts.length > 0 && (
-
-
-
-
-
-
-
- Waste Stock Breakdown
-
-
- Detailed waste analysis
-
-
-
+
+
+ Low Stock Alerts ({lowStockAlerts.length})
-
-
-
-
- After Cleaning
-
- {wasteStock.afterCleaning.total}
-
-
-
-
- After Processing
-
- {wasteStock.afterProcessing.total}
-
+
+ {lowStockAlerts.slice(0, 6).map((alert) => (
+
+ {alert.name}
+ {alert.available} avail / {alert.minReorderLevel} min
-
-
-
- {wasteStock.afterCleaning.details.length > 0 && (
-
-
-
- Cleaning Waste Details
-
-
- {wasteStock.afterCleaning.details
- .slice(0, 3)
- .map((w, idx) => (
-
-
- {w.warehouse?.name || 'N/A'}
-
-
- {w.quantity}
-
-
- ))}
-
-
- )}
-
- {wasteStock.afterProcessing.details.length > 0 && (
-
-
-
- Processing Waste Details
-
-
- {wasteStock.afterProcessing.details
- .slice(0, 3)
- .map((w, idx) => (
-
-
- {w.warehouse?.name || 'N/A'}
-
-
- {w.quantity}
-
-
- ))}
-
-
- )}
-
-
-
-
-
-
- );
-};
-
-interface StatCardProps {
- icon: React.ReactNode;
- label: string;
- value: number;
- unit?: string;
- color: 'blue' | 'orange' | 'purple' | 'green' | 'red' | 'indigo' | 'teal';
- tooltip?: string;
- details?: any[];
- detailsFormatter?: (details: any[]) => string[];
- large?: boolean;
-}
-
-const StatCard: React.FC = ({
- icon,
- label,
- value,
- unit,
- color,
- tooltip,
- details,
- detailsFormatter,
- large = false,
-}) => {
- const [showDetails, setShowDetails] = useState(false);
-
- const colorClasses = {
- blue: {
- bg: 'bg-blue-50',
- icon: 'text-blue-600',
- value: 'text-blue-600',
- border: 'border-blue-200',
- gradient: 'from-blue-500 to-blue-600',
- },
- orange: {
- bg: 'bg-orange-50',
- icon: 'text-orange-600',
- value: 'text-orange-600',
- border: 'border-orange-200',
- gradient: 'from-orange-500 to-orange-600',
- },
- purple: {
- bg: 'bg-purple-50',
- icon: 'text-purple-600',
- value: 'text-purple-600',
- border: 'border-purple-200',
- gradient: 'from-purple-500 to-purple-600',
- },
- green: {
- bg: 'bg-green-50',
- icon: 'text-green-600',
- value: 'text-green-600',
- border: 'border-green-200',
- gradient: 'from-green-500 to-green-600',
- },
- red: {
- bg: 'bg-red-50',
- icon: 'text-red-600',
- value: 'text-red-600',
- border: 'border-red-200',
- gradient: 'from-red-500 to-red-600',
- },
- indigo: {
- bg: 'bg-indigo-50',
- icon: 'text-indigo-600',
- value: 'text-indigo-600',
- border: 'border-indigo-200',
- gradient: 'from-indigo-500 to-indigo-600',
- },
- teal: {
- bg: 'bg-teal-50',
- icon: 'text-teal-600',
- value: 'text-teal-600',
- border: 'border-teal-200',
- gradient: 'from-teal-500 to-teal-600',
- },
- };
-
- const classes = colorClasses[color];
-
- return (
-
- details && setShowDetails(!showDetails)}
- >
-
-
- {details && details.length > 0 && (
-
- )}
-
-
-
-
- {label}
-
-
-
- {value.toLocaleString()}
-
- {unit && (
-
- {unit}
-
- )}
-
-
-
- {showDetails && details && detailsFormatter && (
-
-
- {detailsFormatter(details)
- .slice(0, 3)
- .map((detail, idx) => (
-
- {detail}
-
- ))}
- {detailsFormatter(details).length > 3 && (
-
- +{detailsFormatter(details).length - 3} more items
-
- )}
+ ))}
)}
-
+
);
};
diff --git a/client/src/components/pages/Dashboard/trainingdashboard.tsx b/client/src/components/pages/Dashboard/trainingdashboard.tsx
index 01f88ff..a30bfda 100644
--- a/client/src/components/pages/Dashboard/trainingdashboard.tsx
+++ b/client/src/components/pages/Dashboard/trainingdashboard.tsx
@@ -195,8 +195,40 @@ type ParticipantEngagement = {
const TrainingDashboard: React.FC = () => {
const [selectedYear, setSelectedYear] = useState(new Date().getFullYear());
-
-
+ // Helpers to resolve CSS variables and produce rgba strings for charts
+ const getCssVar = (name: string, fallback: string) => {
+ try {
+ if (typeof window === 'undefined') return fallback;
+ const val = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
+ return val || fallback;
+ } catch (e) {
+ return fallback;
+ }
+ };
+
+ const parseColorToRgba = (color: string, alpha: number) => {
+ if (!color) return `rgba(0,0,0,${alpha})`;
+ color = color.trim();
+ if (color.startsWith('rgba')) return color.replace(/rgba\(([^)]+)\)/, (_, vals) => `rgba(${vals.split(',').slice(0, 3).join(',')},${alpha})`);
+ if (color.startsWith('rgb(')) return color.replace('rgb(', 'rgba(').replace(')', `,${alpha})`);
+ if (color.startsWith('#')) {
+ const hex = color.replace('#', '');
+ const normalized = hex.length === 3 ? hex.split('').map(c => c + c).join('') : hex;
+ const bigint = parseInt(normalized, 16);
+ const r = (bigint >> 16) & 255;
+ const g = (bigint >> 8) & 255;
+ const b = bigint & 255;
+ return `rgba(${r},${g},${b},${alpha})`;
+ }
+ return color;
+ };
+
+ const primaryColor = getCssVar('--primary', '#6366F1');
+ const secondaryColor = getCssVar('--secondary', '#3b82f6');
+ const successColor = getCssVar('--success', '#10b981');
+ const mutedColor = getCssVar('--muted', 'rgba(0,0,0,0.6)');
+
+
// Fetch main dashboard statistics
const {
data: dashboardData,
@@ -214,7 +246,7 @@ const TrainingDashboard: React.FC = () => {
},
staleTime: 5 * 60 * 1000 // 5 minutes
});
-
+
// Fetch feedback statistics
const {
data: feedbackData,
@@ -225,14 +257,14 @@ const TrainingDashboard: React.FC = () => {
queryFn: async () => {
const res = await api.get(API_ROUTES.TRAINING.GET_TRAINING_FEEDBACK_STATS, {
headers: {
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
+ 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
}
});
return res.data.data;
},
staleTime: 5 * 60 * 1000
});
-
+
// Fetch trainer statistics
const {
data: trainerData,
@@ -243,14 +275,14 @@ const TrainingDashboard: React.FC = () => {
queryFn: async () => {
const res = await api.get(API_ROUTES.TRAINING.GET_TRAINING_TRAINER_STATS, {
headers: {
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
+ 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
}
});
return res.data.data;
},
staleTime: 5 * 60 * 1000
});
-
+
// Fetch monthly training statistics
const {
data: monthlyData,
@@ -269,7 +301,7 @@ const TrainingDashboard: React.FC = () => {
},
staleTime: 5 * 60 * 1000
});
-
+
// Fetch attendance statistics
const {
data: attendanceData,
@@ -287,7 +319,7 @@ const TrainingDashboard: React.FC = () => {
},
staleTime: 5 * 60 * 1000
});
-
+
// Fetch participant engagement statistics
const {
data: engagementData,
@@ -305,44 +337,44 @@ const TrainingDashboard: React.FC = () => {
},
staleTime: 5 * 60 * 1000
});
-
+
// Prepare monthly training data for charts
const monthlyTrainingChartData = useMemo(() => {
if (!monthlyData?.months) return null;
-
+
return {
labels: monthlyData.months.map(m => m.monthName),
datasets: [
{
label: 'Total Trainings',
data: monthlyData.months.map(m => m.trainingsCount),
- borderColor: 'rgba(99, 102, 241, 1)',
- backgroundColor: 'rgba(99, 102, 241, 0.2)',
+ borderColor: parseColorToRgba(primaryColor, 1),
+ backgroundColor: parseColorToRgba(primaryColor, 0.2),
fill: true,
tension: 0.4
},
{
label: 'Completed Trainings',
data: monthlyData.months.map(m => m.completedTrainings),
- borderColor: 'rgba(16, 185, 129, 1)',
- backgroundColor: 'rgba(16, 185, 129, 0.2)',
+ borderColor: parseColorToRgba(successColor, 1),
+ backgroundColor: parseColorToRgba(successColor, 0.2),
fill: true,
tension: 0.4
}
]
};
}, [monthlyData]);
-
-
-
+
+
+
// Training status distribution
const statusDistributionData = useMemo(() => {
if (!dashboardData?.summary) return null;
-
+
return {
labels: [
- 'Scheduled',
- 'In Progress',
+ 'Scheduled',
+ 'In Progress',
'Completed'
],
datasets: [
@@ -353,43 +385,43 @@ const TrainingDashboard: React.FC = () => {
dashboardData.summary.completedTrainings
],
backgroundColor: [
- 'rgba(59, 130, 246, 0.8)', // Blue
- 'rgba(245, 158, 11, 0.8)', // Amber
- 'rgba(16, 185, 129, 0.8)' // Green
+ parseColorToRgba(secondaryColor, 0.85),
+ parseColorToRgba(primaryColor, 0.85),
+ parseColorToRgba(successColor, 0.85)
],
borderWidth: 0
}
]
};
}, [dashboardData]);
-
-
-
+
+
+
// Monthly participation chart data
const participationTrendData = useMemo(() => {
if (!engagementData?.monthlyParticipation) return null;
-
+
return {
labels: engagementData.monthlyParticipation.map(item => item.label),
datasets: [
{
label: 'Participants',
data: engagementData.monthlyParticipation.map(item => item.participantsCount),
- borderColor: 'rgba(99, 102, 241, 1)',
- backgroundColor: 'rgba(99, 102, 241, 0.2)',
+ borderColor: parseColorToRgba(primaryColor, 1),
+ backgroundColor: parseColorToRgba(primaryColor, 0.2),
fill: true,
tension: 0.4
}
]
};
}, [engagementData]);
-
-
-
+
+
+
// Detailed feedback ratings data
const detailedFeedbackData = useMemo(() => {
if (!feedbackData?.overallAverages) return null;
-
+
return {
labels: ['Content', 'Trainer', 'Materials', 'Venue', 'Overall'],
datasets: [
@@ -403,11 +435,11 @@ const TrainingDashboard: React.FC = () => {
feedbackData.overallAverages.overall
],
backgroundColor: [
- 'rgba(99, 102, 241, 0.7)',
- 'rgba(16, 185, 129, 0.7)',
- 'rgba(245, 158, 11, 0.7)',
- 'rgba(59, 130, 246, 0.7)',
- 'rgba(139, 92, 246, 0.7)'
+ parseColorToRgba(primaryColor, 0.7),
+ parseColorToRgba(successColor, 0.7),
+ parseColorToRgba(secondaryColor, 0.7),
+ parseColorToRgba(secondaryColor, 0.7),
+ parseColorToRgba(primaryColor, 0.7)
],
borderWidth: 0,
borderRadius: 4,
@@ -416,13 +448,13 @@ const TrainingDashboard: React.FC = () => {
]
};
}, [feedbackData]);
-
+
// Generate year options for dropdown
const yearOptions = useMemo(() => {
const currentYear = new Date().getFullYear();
return Array.from({ length: 5 }, (_, i) => currentYear - i);
}, []);
-
+
// Animation variants
const containerVariants = {
hidden: { opacity: 0 },
@@ -433,7 +465,7 @@ const TrainingDashboard: React.FC = () => {
}
}
};
-
+
const itemVariants = {
hidden: { y: 20, opacity: 0 },
visible: {
@@ -445,9 +477,9 @@ const TrainingDashboard: React.FC = () => {
}
}
};
-
+
return (
- {
{/* Dashboard Header */}
- Training Dashboard
- Comprehensive overview of training programs and metrics
+ Training Dashboard
+ Comprehensive overview of training programs and metrics
-
+
-
+
-
-
+
{/* Top Stats Cards */}
{/* Total Trainings */}
-
-
+
+
- TOTAL TRAININGS
+ TOTAL TRAININGS
-
+
{dashboardLoading ? "..." : dashboardData?.summary.totalTrainings || 0}
-
+
- {dashboardData?.summary.currentMonthTrainings
+ {dashboardData?.summary.currentMonthTrainings
? `${dashboardData.summary.currentMonthTrainings} this month`
: "0 this month"
}
@@ -509,7 +547,7 @@ const TrainingDashboard: React.FC = () => {
-
+
Scheduled: {dashboardData?.summary.scheduledTrainings || 0}
@@ -524,70 +562,81 @@ const TrainingDashboard: React.FC = () => {
-
+
{/* Total Participants */}
-
-
+
+
- PARTICIPANTS
+ PARTICIPANTS
-
+
{dashboardLoading ? "..." : dashboardData?.summary.totalParticipants || 0}
{engagementData && (
-
+
{engagementData.topParticipants.length} active
)}
-
-
-
+
+ p.engagementScore > 70).length /
+ .filter(p => p.engagementScore > 70).length /
engagementData.topParticipants.length) * 100))
- : 0}%`
+ : 0}%`
}}
>
-
+
{/* Satisfaction Rate */}
-
-
+
+
- SATISFACTION RATE
+ SATISFACTION RATE
-
+
{dashboardLoading ? "..." : `${dashboardData?.summary.averageRating.toFixed(1)}/5.0` || "0/5.0"}
{feedbackData?.ratingDistribution && (
-
+
{feedbackData.ratingDistribution.excellent + feedbackData.ratingDistribution.good} positive
)}
-
+
{feedbackData?.ratingDistribution && (
<>
@@ -602,44 +651,50 @@ const TrainingDashboard: React.FC = () => {
)}
-
+
{/* Attendance Rate */}
-
-
+
+
- ATTENDANCE RATE
+ ATTENDANCE RATE
-
- {attendanceLoading ? "..." :
- attendanceData?.statusDistribution ?
- `${Math.round((attendanceData.statusDistribution
- .find(s => s.status === 'PRESENT')?.percentage || 0))}%` :
- "0%"
+
+ {attendanceLoading ? "..." :
+ attendanceData?.statusDistribution ?
+ `${Math.round((attendanceData.statusDistribution
+ .find(s => s.status === 'PRESENT')?.percentage || 0))}%` :
+ "0%"
}
- {attendanceData?.totalAttendance && (
-
- {attendanceData.totalAttendance} records
+ {(attendanceData?.totalAttendance ?? 0) > 0 && (
+
+ {attendanceData?.totalAttendance}
+ {' '}
+ records
)}
-
+
{attendanceData?.statusDistribution && (
attendanceData.statusDistribution.slice(0, 2).map((status, idx) => (
-
+
{status.status}: {status.percentage}%
))
@@ -647,20 +702,21 @@ const TrainingDashboard: React.FC = () => {
-
+
{/* Monthly Training Trend */}
-
+
-
-
+
+
Monthly Training Trend
-
+
{selectedYear}
@@ -675,7 +731,7 @@ const TrainingDashboard: React.FC = () => {
Failed to load monthly data
) : !monthlyTrainingChartData ? (
-
+
No monthly data available
) : (
@@ -689,7 +745,7 @@ const TrainingDashboard: React.FC = () => {
y: {
beginAtZero: true,
grid: {
- color: 'rgba(0, 0, 0, 0.05)',
+ color: parseColorToRgba(mutedColor, 0.05),
},
ticks: {
precision: 0
@@ -722,16 +778,17 @@ const TrainingDashboard: React.FC = () => {
)}
-
+
{/* Training Status Distribution */}
-
+
-
-
+
+
Status Distribution
@@ -746,7 +803,7 @@ const TrainingDashboard: React.FC = () => {
Failed to load status data
) : !statusDistributionData ? (
-
+
No status data available
) : (
@@ -772,8 +829,8 @@ const TrainingDashboard: React.FC = () => {
/>
{dashboardData?.summary && (
- {dashboardData.summary.totalTrainings}
- Total
+ {dashboardData.summary.totalTrainings}
+ Total
)}
@@ -782,17 +839,18 @@ const TrainingDashboard: React.FC = () => {
-
+
{/* Upcoming Trainings */}
-
+
-
-
+
+
Upcoming Trainings
View All
@@ -808,21 +866,20 @@ const TrainingDashboard: React.FC = () => {
Failed to load upcoming trainings
) : !dashboardData?.upcomingTrainings || dashboardData.upcomingTrainings.length === 0 ? (
-
-
+
+
No upcoming trainings scheduled
) : (
-
+
{dashboardData.upcomingTrainings.map((training) => (
-
+
-
@@ -833,10 +890,10 @@ const TrainingDashboard: React.FC = () => {
-
+
- {training.title}
-
+ {training.title}
+
{training.trainerName}
@@ -851,25 +908,25 @@ const TrainingDashboard: React.FC = () => {
-
{training.trainingType}
-
- {training.daysUntilStart > 0 ?
- `${training.daysUntilStart} day${training.daysUntilStart !== 1 ? 's' : ''} remaining` :
+
+ {training.daysUntilStart > 0 ?
+ `${training.daysUntilStart} day${training.daysUntilStart !== 1 ? 's' : ''} remaining` :
'Today'
}
-
-
+
+
@@ -879,16 +936,17 @@ const TrainingDashboard: React.FC = () => {
)}
-
+
{/* Trainer Performance */}
-
+
-
-
+
+
Trainer Performance
View All
@@ -904,55 +962,54 @@ const TrainingDashboard: React.FC = () => {
Failed to load trainer data
) : !trainerData?.trainers || trainerData.trainers.length === 0 ? (
-
+
No trainer data available
) : (
-
+
- |
+ |
Trainer
|
-
+ |
Trainings
|
-
+ |
Rating
|
-
+ |
Completion
|
-
+
{trainerData.trainers.slice(0, 6).map((trainer) => (
-
+
{trainer.name.charAt(0).toUpperCase()}
- {trainer.name}
- {trainer.email}
+ {trainer.name}
+ {trainer.email}
|
- {trainer.trainingsCount}
- {trainer.completedTrainings} completed
+ {trainer.trainingsCount}
+ {trainer.completedTrainings} completed
|
- = 4.5 ? 'bg-green-100 text-green-800' :
+ = 4.5 ? 'bg-green-100 text-green-800' :
trainer.ratings.overall >= 3.5 ? 'bg-blue-100 text-blue-800' :
- trainer.ratings.overall >= 2.5 ? 'bg-amber-100 text-amber-800' :
- 'bg-red-100 text-red-800'
- }`}
+ trainer.ratings.overall >= 2.5 ? 'bg-amber-100 text-amber-800' :
+ 'bg-red-100 text-red-800'
+ }`}
>
{trainer.ratings.overall.toFixed(1)}
@@ -960,15 +1017,14 @@ const TrainingDashboard: React.FC = () => {
|
- {trainer.completionRate}%
-
- = 80 ? 'bg-green-500' :
+ {trainer.completionRate}%
+
+ = 80 ? 'bg-green-500' :
trainer.completionRate >= 60 ? 'bg-blue-500' :
- trainer.completionRate >= 40 ? 'bg-amber-500' :
- 'bg-red-500'
- }`}
+ trainer.completionRate >= 40 ? 'bg-amber-500' :
+ 'bg-red-500'
+ }`}
style={{ width: `${trainer.completionRate}%` }}
>
@@ -982,17 +1038,18 @@ const TrainingDashboard: React.FC = () => {
-
+
{/* Feedback Ratings Breakdown */}
-
+
-
-
+
+
Feedback Ratings Breakdown
@@ -1007,7 +1064,7 @@ const TrainingDashboard: React.FC = () => {
Failed to load feedback data
) : !detailedFeedbackData ? (
-
+
No feedback data available
) : (
@@ -1028,7 +1085,7 @@ const TrainingDashboard: React.FC = () => {
beginAtZero: true,
max: 5,
grid: {
- color: 'rgba(0, 0, 0, 0.05)',
+ color: parseColorToRgba(mutedColor, 0.05),
},
ticks: {
stepSize: 1
@@ -1051,16 +1108,17 @@ const TrainingDashboard: React.FC = () => {
)}
-
+
{/* Participant Engagement */}
-
+
-
-
+
+
Participant Engagement
@@ -1075,73 +1133,70 @@ const TrainingDashboard: React.FC = () => {
Failed to load engagement data
) : !engagementData?.topParticipants || engagementData.topParticipants.length === 0 ? (
-
+
No engagement data available
) : (
{engagementData.topParticipants.slice(0, 5).map((participant, idx) => (
-
-
+ idx === 2 ? 'bg-green-600' :
+ 'bg-gray-600'
+ }`}>
{participant.name.charAt(0).toUpperCase()}
- {participant.name}
- {participant.organization}
+ {participant.name}
+ {participant.organization}
- = 80 ? 'bg-green-100 text-green-800' :
+ = 80 ? 'bg-green-100 text-green-800' :
participant.engagementScore >= 60 ? 'bg-blue-100 text-blue-800' :
- participant.engagementScore >= 40 ? 'bg-amber-100 text-amber-800' :
- 'bg-red-100 text-red-800'
- }`}
+ participant.engagementScore >= 40 ? 'bg-amber-100 text-amber-800' :
+ 'bg-red-100 text-red-800'
+ }`}
>
{participant.engagementScore}%
-
+
Attendance:
{participant.attendanceRate}%
-
- = 80 ? 'bg-green-500' :
+
+ = 80 ? 'bg-green-500' :
participant.attendanceRate >= 60 ? 'bg-blue-500' :
- participant.attendanceRate >= 40 ? 'bg-amber-500' :
- 'bg-red-500'
- }`}
+ participant.attendanceRate >= 40 ? 'bg-amber-500' :
+ 'bg-red-500'
+ }`}
style={{ width: `${participant.attendanceRate}%` }}
>
-
+
Feedback:
{participant.feedbackRate}%
-
- = 80 ? 'bg-green-500' :
+ | | |