-
+
+
@@ -231,6 +245,6 @@ export function CardSkeleton() {
}
// Divider
-export function Divider({ className = '' }: { className?: string }) {
- return
;
+export function Divider({ className }: { className?: string }) {
+ return
;
}
diff --git a/frontend/src/lib/animations.ts b/frontend/src/lib/animations.ts
new file mode 100644
index 0000000..75c33cd
--- /dev/null
+++ b/frontend/src/lib/animations.ts
@@ -0,0 +1,37 @@
+export const pageVariants = {
+ initial: {
+ opacity: 0,
+ y: 8,
+ scale: 0.99,
+ },
+ animate: {
+ opacity: 1,
+ y: 0,
+ scale: 1,
+ transition: {
+ duration: 0.4,
+ ease: [0.16, 1, 0.3, 1], // expo out
+ staggerChildren: 0.1,
+ },
+ },
+ exit: {
+ opacity: 0,
+ y: -8,
+ transition: {
+ duration: 0.2,
+ ease: [0.7, 0, 0.84, 0], // ease in
+ },
+ },
+};
+
+export const itemVariants = {
+ initial: { opacity: 0, y: 10 },
+ animate: {
+ opacity: 1,
+ y: 0,
+ transition: {
+ duration: 0.3,
+ ease: [0.16, 1, 0.3, 1],
+ },
+ },
+};
diff --git a/frontend/src/pages/AuditLogPage.tsx b/frontend/src/pages/AuditLogPage.tsx
index 894d09c..828d137 100644
--- a/frontend/src/pages/AuditLogPage.tsx
+++ b/frontend/src/pages/AuditLogPage.tsx
@@ -1,22 +1,26 @@
import { useState, useEffect } from 'react';
import {
- Shield, Search, Filter, Download, ChevronLeft, ChevronRight,
- User, Clock, Eye, X, Loader2, FileText,
- LogIn, LogOut, Edit, Trash2, FileDown, ThumbsUp, ThumbsDown
+ Shield, Search, Filter, Download,
+ LogIn, LogOut, Edit, Trash2, FileDown, ThumbsUp, ThumbsDown,
+ Eye, Clock, User
} from 'lucide-react';
import { auditApi, AuditLog, AuditLogSummary, AuditLogFilters } from '../lib/auditApi';
import { PageHeader, MetricCard, EmptyState } from '../components/ui/LinearPrimitives';
+import { DataTable, Column } from '../components/ui/DataTable';
+import { cn } from '../lib/utils';
-const ACTION_CONFIG: Record
= {
- CREATE: { icon: FileText, badge: 'badge-success', label: 'Created' },
- UPDATE: { icon: Edit, badge: 'badge-accent', label: 'Updated' },
- DELETE: { icon: Trash2, badge: 'badge-destructive', label: 'Deleted' },
- LOGIN: { icon: LogIn, badge: 'badge-accent', label: 'Login' },
- LOGOUT: { icon: LogOut, badge: 'badge', label: 'Logout' },
- ACCESS: { icon: Eye, badge: 'badge-accent', label: 'Accessed' },
- EXPORT: { icon: FileDown, badge: 'badge-warning', label: 'Exported' },
- APPROVE: { icon: ThumbsUp, badge: 'badge-success', label: 'Approved' },
- REJECT: { icon: ThumbsDown, badge: 'badge-destructive', label: 'Rejected' }
+// --- Configuration ---
+
+const ACTION_CONFIG: Record = {
+ CREATE: { icon: ThumbsUp, color: 'text-emerald-500 bg-emerald-500/10', label: 'Create' },
+ UPDATE: { icon: Edit, color: 'text-blue-500 bg-blue-500/10', label: 'Update' },
+ DELETE: { icon: Trash2, color: 'text-rose-500 bg-rose-500/10', label: 'Delete' },
+ LOGIN: { icon: LogIn, color: 'text-cyan-500 bg-cyan-500/10', label: 'Login' },
+ LOGOUT: { icon: LogOut, color: 'text-slate-500 bg-slate-500/10', label: 'Logout' },
+ ACCESS: { icon: Eye, color: 'text-indigo-500 bg-indigo-500/10', label: 'Access' },
+ EXPORT: { icon: FileDown, color: 'text-amber-500 bg-amber-500/10', label: 'Export' },
+ APPROVE: { icon: ThumbsUp, color: 'text-emerald-500 bg-emerald-500/10', label: 'Approve' },
+ REJECT: { icon: ThumbsDown, color: 'text-rose-500 bg-rose-500/10', label: 'Reject' }
};
const ENTITY_LABELS: Record = {
@@ -25,6 +29,8 @@ const ENTITY_LABELS: Record = {
Plant: 'Plant', Supply: 'Supply'
};
+// --- Page Component ---
+
export default function AuditLogPage() {
const [logs, setLogs] = useState([]);
const [summary, setSummary] = useState(null);
@@ -33,8 +39,6 @@ export default function AuditLogPage() {
const [selectedLog, setSelectedLog] = useState(null);
const [filters, setFilters] = useState({ page: 1, limit: 25 });
- const [showFilters, setShowFilters] = useState(false);
- const [searchTerm, setSearchTerm] = useState('');
const [pagination, setPagination] = useState({ page: 1, limit: 25, total: 0, pages: 0 });
useEffect(() => {
@@ -81,328 +85,207 @@ export default function AuditLogPage() {
}
}
- function formatTimestamp(ts: string) {
- const date = new Date(ts);
- const now = new Date();
- const diffMs = now.getTime() - date.getTime();
- const diffMins = Math.floor(diffMs / 60000);
- const diffHours = Math.floor(diffMins / 60);
- const diffDays = Math.floor(diffHours / 24);
+ // --- Table Definition ---
- if (diffMins < 1) return 'Just now';
- if (diffMins < 60) return `${diffMins}m ago`;
- if (diffHours < 24) return `${diffHours}h ago`;
- if (diffDays < 7) return `${diffDays}d ago`;
- return date.toLocaleDateString();
- }
-
- function getActionConfig(action: string) {
- return ACTION_CONFIG[action] || { icon: FileText, badge: 'badge', label: action };
- }
+ const columns: Column[] = [
+ {
+ key: 'timestamp',
+ header: 'Timestamp',
+ cell: (log) => (
+
+
+ {new Date(log.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
+
+
+ {new Date(log.timestamp).toLocaleDateString()}
+
+
+ )
+ },
+ {
+ key: 'userName',
+ header: 'User',
+ cell: (log) => (
+
+
+
+
+
+ {log.userName || 'System'}
+
+
+ )
+ },
+ {
+ key: 'action',
+ header: 'Action',
+ cell: (log) => {
+ const config = ACTION_CONFIG[log.action] || { icon: Shield, color: 'text-slate-500 bg-slate-100', label: log.action };
+ const Icon = config.icon;
+ return (
+
+
+ {config.label}
+
+ );
+ }
+ },
+ {
+ key: 'entity',
+ header: 'Target',
+ cell: (log) => (
+
+
+ {ENTITY_LABELS[log.entity] || log.entity}
+
+ {log.entityName && (
+
+ {log.entityName}
+
+ )}
+
+ )
+ },
+ {
+ key: 'details',
+ header: 'Details',
+ hideOnMobile: true,
+ cell: (log) => (
+
+ {log.changes ? `Updated ${Object.keys(log.changes).join(', ')}` :
+ log.ipAddress ? `IP: ${log.ipAddress}` : '—'}
+
+ )
+ }
+ ];
return (
-
+
-
-
-
+
}
/>
- {/* Summary Cards */}
+ {/* Metrics Row */}
{summary && (
-
+
-
+
)}
- {/* Filters Panel */}
- {showFilters && (
-
-
-
- {(filters.entity || filters.action || filters.startDate || filters.endDate) && (
-
- )}
-
- )}
-
- {/* Logs Table */}
-
- {loading ? (
-
-
-
- ) : logs.length === 0 ? (
+ {/* Unified Table View */}
+
setSelectedLog(log)}
+ emptyState={
- ) : (
- <>
-
-
-
-
- | Time |
- User |
- Action |
- Entity |
- Details |
- View |
-
-
-
- {logs.map(log => {
- const config = getActionConfig(log.action);
- return (
- setSelectedLog(log)}
- >
- |
-
-
- {formatTimestamp(log.timestamp)}
-
-
- {new Date(log.timestamp).toLocaleString()}
-
- |
-
-
-
-
-
-
- {log.userName || 'System'}
-
-
- |
-
-
- {config.label}
-
- |
-
-
- {ENTITY_LABELS[log.entity] || log.entity}
-
- {log.entityName && (
- {log.entityName}
- )}
- |
-
-
- {log.changes ? `Changed: ${Object.keys(log.changes).join(', ')}` :
- log.ipAddress ? `IP: ${log.ipAddress}` : '—'}
-
- |
-
-
- |
-
- );
- })}
-
-
-
+ }
+ />
- {/* Pagination */}
-
-
- {((pagination.page - 1) * pagination.limit) + 1}–{Math.min(pagination.page * pagination.limit, pagination.total)} of {pagination.total}
-
-
-
- {pagination.page}/{pagination.pages}
-
-
-
- >
- )}
+ {/* Simplified Pagination Footer */}
+
+
+ Showing {((pagination.page - 1) * pagination.limit) + 1}–{Math.min(pagination.page * pagination.limit, pagination.total)} of {pagination.total} events
+
+
+
+
+ {pagination.page} / {pagination.pages}
+
+
+
- {/* Detail Modal */}
+ {/* Detail View Modal (Keep existing logic but styled) */}
{selectedLog && (
- setSelectedLog(null)}>
+
setSelectedLog(null)}>
e.stopPropagation()}
>
-
+
-
Audit Log Detail
-
ID: {selectedLog.id.slice(0, 8)}...
+
Event Details
+
{selectedLog.id}
-
-
-
-
-
Timestamp
-
{new Date(selectedLog.timestamp).toLocaleString()}
-
-
-
User
-
{selectedLog.userName || 'System'}
-
-
-
Action
-
- {getActionConfig(selectedLog.action).label}
-
-
-
-
Entity
-
- {ENTITY_LABELS[selectedLog.entity] || selectedLog.entity}
- {selectedLog.entityName && ({selectedLog.entityName})}
-
-
-
- {selectedLog.ipAddress && (
-
-
IP Address
-
{selectedLog.ipAddress}
-
- )}
-
- {selectedLog.changes && Object.keys(selectedLog.changes).length > 0 && (
-
-
Changes
-
- {Object.entries(selectedLog.changes).map(([key, value]: [string, any]) => (
-
-
{key}:
-
{JSON.stringify(value.from)}
-
→
-
{JSON.stringify(value.to)}
+
+ {/* Change set visualization */}
+ {selectedLog.changes && Object.keys(selectedLog.changes).length > 0 ? (
+
+
Property Changes
+
+ {Object.entries(selectedLog.changes).map(([key, val]: [string, any]) => (
+
+
{key}
+
+
+ {JSON.stringify(val.from)}
+
+
+
+ {JSON.stringify(val.to)}
+
+
))}
- )}
-
- {(selectedLog.before || selectedLog.after) && (
-
- {selectedLog.before && (
-
-
Before
-
- {JSON.stringify(selectedLog.before, null, 2)}
-
-
- )}
- {selectedLog.after && (
-
-
After
-
- {JSON.stringify(selectedLog.after, null, 2)}
-
-
- )}
+ ) : (
+
+
+
No attribute-level changes recorded for this event.
)}
+
+
+
+
Actor
+
{selectedLog.userName || 'System Processor'}
+
+
+
Source IP
+
{selectedLog.ipAddress || 'Internal'}
+
+
diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx
index 3d8580b..4a03577 100644
--- a/frontend/src/pages/LoginPage.tsx
+++ b/frontend/src/pages/LoginPage.tsx
@@ -1,9 +1,11 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Shield, ArrowRight, Loader2, Key, ChevronRight, Lock } from 'lucide-react';
import api from '../lib/api';
import { useAuth } from '../context/AuthContext';
import { DevTools } from '../components/dev/DevTools';
-import { Loader2, ArrowRight } from 'lucide-react';
+import { pageVariants, itemVariants } from '../lib/animations';
export default function LoginPage() {
const [email, setEmail] = useState('');
@@ -13,9 +15,8 @@ export default function LoginPage() {
const { login } = useAuth();
const navigate = useNavigate();
- // Set page title
useEffect(() => {
- document.title = '777 Wolfpack - Login';
+ document.title = 'Ersen OS | Authentication';
}, []);
const handleSubmit = async (e: React.FormEvent) => {
@@ -26,127 +27,156 @@ export default function LoginPage() {
try {
const { data } = await api.post('/auth/login', { email, password });
login(data.accessToken, data.refreshToken, data.user);
- navigate('/');
+ navigate('/dashboard');
} catch (err: any) {
- setError(err.response?.data?.message || 'Login failed');
+ setError(err.response?.data?.message || 'Authentication failed. Please check your credentials.');
} finally {
setIsLoading(false);
}
};
return (
-
- {/* Background subtle pattern */}
-
+
+ {/* Left Side: Brand/Visual */}
+
+
- {/* Main container */}
-
- {/* Logo */}
-
-
-

- {/* Subtle glow on hover */}
-
+
+
+
-
-
- {/* Card */}
-
- {/* Header */}
-
-
- Welcome back
-
-
- Sign in to 777 Wolfpack
+
+
+ ERSEN OS
+
+
+ Operational Infrastructure
- {/* Form */}
-
);
diff --git a/frontend/src/pages/MetrcDashboardPage.tsx b/frontend/src/pages/MetrcDashboardPage.tsx
index 2900066..5f8b585 100644
--- a/frontend/src/pages/MetrcDashboardPage.tsx
+++ b/frontend/src/pages/MetrcDashboardPage.tsx
@@ -2,26 +2,24 @@ import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import {
Cloud, CloudOff, RefreshCw, Download, AlertTriangle,
- CheckCircle, XCircle, Clock, MapPin, ArrowUpDown,
- FileText, Loader2, ExternalLink, Filter, Box
+ CheckCircle, Clock, MapPin, ArrowUpDown,
+ FileText, ExternalLink, Filter, Box
} from 'lucide-react';
-import { metrcApi, MetrcLocation, MetrcDiscrepancy, MetrcReportResponse, MetrcAuditResponse } from '../lib/metrcApi';
-import { PageHeader, EmptyState, CardSkeleton } from '../components/ui/LinearPrimitives';
-import Hero1 from '../components/aura/Hero';
+import { metrcApi, MetrcReportResponse, MetrcAuditResponse } from '../lib/metrcApi';
+import { PageHeader, EmptyState, MetricCard } from '../components/ui/LinearPrimitives';
+import { DataTable, Column } from '../components/ui/DataTable';
+import { cn } from '../lib/utils';
+
+// --- Configuration ---
const SEVERITY_CONFIG = {
- CRITICAL: { color: 'text-red-600 bg-red-100 dark:bg-red-900/30', label: 'Critical' },
- HIGH: { color: 'text-orange-600 bg-orange-100 dark:bg-orange-900/30', label: 'High' },
- MEDIUM: { color: 'text-yellow-600 bg-yellow-100 dark:bg-yellow-900/30', label: 'Medium' },
- LOW: { color: 'text-blue-600 bg-blue-100 dark:bg-blue-900/30', label: 'Low' }
+ CRITICAL: { color: 'text-rose-500 bg-rose-500/10', label: 'Critical' },
+ HIGH: { color: 'text-amber-500 bg-amber-500/10', label: 'High' },
+ MEDIUM: { color: 'text-yellow-500 bg-yellow-500/10', label: 'Medium' },
+ LOW: { color: 'text-blue-500 bg-blue-500/10', label: 'Low' }
};
-const DISCREPANCY_TYPE_LABELS = {
- MISSING_IN_METRC: 'Missing in METRC',
- MISSING_LOCALLY: 'Missing Locally',
- LOCATION_MISMATCH: 'Location Mismatch',
- STATUS_MISMATCH: 'Status Mismatch'
-};
+// --- Page Component ---
export default function MetrcDashboardPage() {
const [loading, setLoading] = useState(true);
@@ -83,451 +81,236 @@ export default function MetrcDashboardPage() {
p.room.toLowerCase().includes(searchTerm.toLowerCase())
) || [];
- // METRC connection status (simulated - would check actual API in production)
- const metrcEnabled = false; // Set via env var in production
- const connectionStatus = metrcEnabled ? 'connected' : 'demo';
+ // --- Table Column Definitions ---
+
+ const plantColumns: Column
[] = [
+ {
+ key: 'tagNumber',
+ header: 'Plant Tag',
+ cell: (plant) => (
+
+ {plant.tagNumber}
+ METRC ID
+
+ )
+ },
+ {
+ key: 'location',
+ header: 'METRC Location',
+ cell: (plant) => {plant.location || '—'}
+ },
+ {
+ key: 'room',
+ header: 'Local Room',
+ cell: (plant) => {plant.room}
+ },
+ {
+ key: 'status',
+ header: 'Status',
+ className: 'text-center',
+ cell: () => (
+
+
+ Synced
+
+ )
+ },
+ {
+ key: 'actions',
+ header: '',
+ className: 'text-right',
+ cell: (plant) => (
+
+ { e.stopPropagation(); setSelectedHistoryPlant(plant); }}
+ className="p-1.5 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-md text-slate-500"
+ >
+
+
+
+
+
+
+ )
+ }
+ ];
+
+ const auditColumns: Column[] = [
+ {
+ key: 'plantTag',
+ header: 'Plant Tag',
+ cell: (move) => {move.plantTag}
+ },
+ {
+ key: 'from',
+ header: 'Previous Room',
+ cell: (move) => {move.from}
+ },
+ {
+ key: 'to',
+ header: 'New Room',
+ cell: (move) => {move.to}
+ },
+ {
+ key: 'movedAt',
+ header: 'Timestamp',
+ cell: (move) => (
+
+ {new Date(move.movedAt).toLocaleDateString()}
+ {new Date(move.movedAt).toLocaleTimeString()}
+
+ )
+ }
+ ];
return (
-
-
+
+
+
+
+ Sync All
+
+
+
+ Export CSV
+
+
+ }
+ />
-
- {/* Connection Status Banner */}
-
-
- {connectionStatus === 'demo' ? (
- <>
-
-
-
Demo Mode
-
- METRC API not connected. Data shown is for demonstration purposes.
-
-
-
-
- API Docs
-
- >
- ) : (
- <>
-
-
-
Connected to METRC
-
- Last sync: {lastSync ? lastSync.toLocaleString() : 'Never'}
-
-
-
- >
+ {/* Connection Banner */}
+
+
+
+
+
+
Sandbox / Demo Environment
+
The METRC production API is disabled. You are viewing simulated system data.
+
+
+ API Docs
+
+
+
+ {/* Metrics */}
+ {report && (
+
+
+
+
+
+
+ )}
+
+ {/* Tabs */}
+
+ {['overview', 'plants', 'audit'].map(tab => (
+ setActiveTab(tab as any)}
+ className={cn(
+ "px-6 py-2 rounded-lg text-xs font-bold uppercase tracking-widest transition-all",
+ activeTab === tab
+ ? "bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 shadow-sm"
+ : "text-slate-400 hover:text-slate-600 dark:hover:text-slate-200"
)}
+ >
+ {tab}
+
+ ))}
+
+
+ {/* Tab Content */}
+ {activeTab === 'overview' && (
+
+
+
Recent Compliance Events
+
+
+
+
+
+
+
+
System in Sync
+
No discrepancies detected between local state and METRC tracking ID logs.
+
+
Download Full Reconciliation Report
+ )}
- {/* Tab Navigation */}
-
- {
- [
- { id: 'overview', label: 'Overview' },
- { id: 'plants', label: 'Plant Locations' },
- { id: 'audit', label: 'Audit Trail' }
- ].map(tab => (
- setActiveTab(tab.id as any)}
- className={`flex-1 py-2 px-4 rounded-md text-sm font-medium transition-colors ${activeTab === tab.id
- ? 'bg-primary text-primary shadow-sm'
- : 'text-secondary hover:text-primary'
- }`}
- >
- {tab.label}
-
- ))
- }
-
-
- {
- loading ? (
-
- {Array.from({ length: 4 }).map((_, i) =>
)}
+ {activeTab === 'plants' && (
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
- ) : (
- <>
- {/* Overview Tab */}
- {activeTab === 'overview' && (
-
- {/* Stats Grid */}
-
-
-
-
-
-
-
-
{report?.plantCount || 0}
-
Total Plants
-
-
-
+
+
+ }
+ />
+
+ )}
-
-
-
-
-
{audit?.summary.totalMoves || 0}
-
Plant Moves (30d)
-
-
-
+ {activeTab === 'audit' && (
+
+
+
+ )}
-
-
-
-
-
- {/* Quick Actions */}
-
-
Quick Actions
-
-
-
-
-
Export CSV
-
For manual METRC upload
-
-
-
-
-
-
-
Sync All Plants
-
Update all locations
-
-
-
-
setActiveTab('audit')}>
-
-
-
View Audit
-
Compliance report
-
-
-
-
-
-
-
Open METRC
-
State portal
-
-
-
-
-
- {/* Recent Moves */}
- {audit && audit.recentMoves.length > 0 && (
-
-
-
Recent Plant Moves
-
Location changes requiring METRC update
-
-
- {audit.recentMoves.slice(0, 5).map((move: any, i: number) => (
-
-
-
-
{move.plantTag}
-
- {move.from} → {move.to}
-
-
-
-
- {new Date(move.movedAt).toLocaleDateString()}
-
-
-
- ))}
-
-
- )}
-
- )}
-
- {/* Plants Tab */}
- {activeTab === 'plants' && (
-
- {/* Search */}
-
-
-
- setSearchTerm(e.target.value)}
- className="input w-full pl-9"
- />
-
-
-
- {/* Plant List */}
- {filteredPlants.length === 0 ? (
-
- ) : (
-
-
-
-
-
- | Tag |
- METRC Location |
- Room |
- Section |
- Position |
- Status |
- Actions |
-
-
-
- {filteredPlants.slice(0, 50).map((plant: any) => (
-
- | {plant.tagNumber} |
- {plant.location || '-'} |
- {plant.room} |
- {plant.section || '-'} |
- {plant.position || '-'} |
-
-
-
- Synced
-
- |
-
-
- setSelectedHistoryPlant(plant)}
- className="btn btn-ghost btn-sm btn-square"
- title="View History"
- >
-
-
-
-
-
-
- |
-
- ))}
-
-
-
- {filteredPlants.length > 50 && (
-
- Showing 50 of {filteredPlants.length} plants
-
- )}
-
- )}
-
- )}
-
- {/* Audit Tab */}
- {activeTab === 'audit' && audit && (
-
- {/* Audit Summary */}
-
-
-
-
METRC Compliance Audit
-
- {new Date(audit.dateRange.start).toLocaleDateString()} - {new Date(audit.dateRange.end).toLocaleDateString()}
-
-
-
-
- Compliant
-
-
-
-
-
-
{audit.summary.totalPlants}
-
Total Plants
-
-
-
{audit.summary.totalMoves}
-
Location Changes
-
-
-
{audit.summary.uniquePlantsMoved}
-
Plants Moved
-
-
-
-
- {/* Recent Moves Table */}
-
-
-
Location Change History
-
- {audit.recentMoves.length === 0 ? (
-
-
-
No location changes in this period
-
- ) : (
-
-
-
-
- | Plant Tag |
- From |
- To |
- Date |
- Reason |
-
-
-
- {audit.recentMoves.map((move: any, i: number) => (
-
- | {move.plantTag} |
- {move.from} |
- {move.to} |
-
- {new Date(move.movedAt).toLocaleDateString()}
- |
- {move.reason || '-'} |
-
- ))}
-
-
-
- )}
-
-
- )}
- >
- )
- }
- {/* Plant History Modal */}
- {
- selectedHistoryPlant && (
-
-
-
-
-
Plant History
-
{selectedHistoryPlant.tagNumber}
-
-
setSelectedHistoryPlant(null)}
- className="btn btn-ghost btn-sm btn-square"
- >
-
-
-
-
-
- {/* Current Status */}
-
-
-
Current Room
-
{selectedHistoryPlant.room}
-
-
-
Current Section
-
{selectedHistoryPlant.section || 'N/A'}
-
-
-
- {/* Timeline */}
-
-
Movement Log
- {audit?.recentMoves.filter((m: any) => m.plantTag === selectedHistoryPlant.tagNumber).length === 0 ? (
-
-
-
No recorded movements found for this plant.
-
- ) : (
-
- {audit?.recentMoves
- .filter((m: any) => m.plantTag === selectedHistoryPlant.tagNumber)
- .sort((a: any, b: any) => new Date(b.movedAt).getTime() - new Date(a.movedAt).getTime())
- .map((move: any, i: number) => (
- -
-
-
-
- Moved to {move.to}
-
-
- From {move.from} • Reason: {move.reason || 'Routine Maintenance'}
-
-
- ))}
-
- )}
-
-
-
-
-
-
- Locate in 3D
-
-
+ {/* History Modal placeholder (Styled) */}
+ {selectedHistoryPlant && (
+
setSelectedHistoryPlant(null)}>
+
e.stopPropagation()}>
+
+
+
+
Plant Lifecycle History
+
Tracking history for {selectedHistoryPlant.tagNumber}
+
+ {/* Simplified timeline */}
+
Historical chain-of-custody logs are being processed...
+
+
setSelectedHistoryPlant(null)} className="btn btn-primary w-full">Close View
- )
- }
-
+
+
+ )}
);
}