diff --git a/docs/AURA_UI_PLAN.md b/docs/AURA_UI_PLAN.md new file mode 100644 index 0000000..47e3c40 --- /dev/null +++ b/docs/AURA_UI_PLAN.md @@ -0,0 +1,64 @@ +# AuraUI Integration Plan and Audit + +## Snapshot Info + +- **Commit Hash**: `da5af8c` (Before AuraUI) +- **Rollback Command**: `git reset --hard da5af8c && git push -f origin main` + +## Component Audit + +We have selected the following components from the [AuraUI Library](https://auraui.com) to enhance the `ca-grow-ops-manager`: + +### 1. 3D Digital Twin Hero (`feature-4-3d`) + +- **Visuals**: Dark mode, floating 3D elements (cubes/spheres) with lime/purple accents. +- **Use Case**: **Facility 3D Viewer Loading/Landing State**. +- **Reason**: Perfectly aligns with the "Digital Twin" core pillar. Replaces the generic "Loading..." spinner with a technical, high-end visual. + +### 2. High-Trust Hero (`hero-1`) + +- **Visuals**: Centered H1, clear subtext, primary/secondary action buttons. +- **Use Case**: **Main Dashboard Header**. +- **Reason**: Provides immediate access to critical actions ("Emergency Stop", "Metrc Sync") and sets the tone for the application. + +### 3. Split-Screen Auth Form (`signup-1`) + +- **Visuals**: Left side form, Right side high-quality operational image. +- **Use Case**: **Visitor Kiosk Check-In** and **SOP Acknowledgement**. +- **Reason**: The "Compliance" aspect needs to feel authoritative and polished. The split view allows us to show legal text/images alongside the signature input. + +### 4. Minimal Navbar (`header-6`) + +- **Visuals**: Dark background, subtle hover effects, active state indicators. +- **Use Case**: **Global App Shell Navigation**. +- **Reason**: The current sidebar is functional but bulky. Moving to a sleek top-nav (or refined side-nav) using these styles cleans up screen real estate for the 3D viewer. + +### 5. Bento Grid (`feature-14`) + +- **Visuals**: Grid of varied-size cards, bento-box style. +- **Use Case**: **Operations Dashboard**. +- **Reason**: We have too many disparate metrics (Temp, Humidity, Task Count, Compliance Status). A bento grid unifies them into a single "Command Center" view. + +## Integration Strategy + +### Prerequisites (Checked) + +- [x] `clsx` (Installed) +- [x] `tailwind-merge` (Installed) +- [x] `class-variance-authority` (Installed) +- [x] `lucide-react` (Installed) +- [ ] `framer-motion` (Missing - **Install Required**) + +### Implementation Order + +1. **Install `framer-motion`**: Required for AuraUI animations. +2. **Setup Utils**: Ensure `lib/utils.ts` has the standard `cn()` helper (it likely does, but verify). +3. **Implement `Hero-1`**: Replace the Dashboard header first as a low-risk, high-impact test. +4. **Implement `Feature-4-3d`**: Add to the 3D Viewer page. + +### Rollback Plan + +If the new components break the build or styling: + +1. Revert to the snapshot commit. +2. Clean `node_modules` if `framer-motion` causes issues. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 92b8a05..d47cf52 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "browser-image-compression": "^2.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.23.26", "i18next": "^25.7.2", "i18next-browser-languagedetector": "^8.2.0", "immer": "^11.0.1", @@ -5200,6 +5201,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.23.26", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz", + "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6577,6 +6605,21 @@ "dev": true, "license": "MIT" }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 718a7af..d0b2ca9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "browser-image-compression": "^2.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.23.26", "i18next": "^25.7.2", "i18next-browser-languagedetector": "^8.2.0", "immer": "^11.0.1", @@ -58,4 +59,4 @@ "vite": "^5.0.8", "vitest": "^1.0.0" } -} \ No newline at end of file +} diff --git a/frontend/src/components/aura/Hero.tsx b/frontend/src/components/aura/Hero.tsx new file mode 100644 index 0000000..9a0dab7 --- /dev/null +++ b/frontend/src/components/aura/Hero.tsx @@ -0,0 +1,245 @@ +import { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Download, + Github, + Sparkles, + Loader, + Copy, + Check, + StopCircle, + Activity, + Leaf +} from "lucide-react"; +import { Card, CardContent } from "@/components/ui/card"; +import { cn } from "@/lib/utils"; + +/** + * AuraUI Hero 1 Component + * Customized for CA Grow Ops Manager + * Displays the main dashboard header with 'Digital Command Center' branding + */ +const Hero1 = () => { + const [isVisible, setIsVisible] = useState(false); + const [currentSentence, setCurrentSentence] = useState(0); + const [isAnimating, setIsAnimating] = useState(false); + const [showCorrected, setShowCorrected] = useState(false); + const [typingText, setTypingText] = useState(""); + const [isTyping, setIsTyping] = useState(false); + const [isSelected, setIsSelected] = useState(false); + const [showPopup, setShowPopup] = useState(false); + + // Customized demo text for Grow Ops context + const demoSentences = [ + { + original: "Room 4 temp is 82 humidity 65 check veg lighting", + corrected: + "Room 4: Temp 82°F, Humidity 65%. Veg lighting inspection required.", + }, + { + original: "feed batch 156 with heavy bloom schedule today", + corrected: + "Action: Feed Batch B-23-156. Schedule: Heavy Bloom. Date: Today.", + }, + { + original: "visitor arrived for checkin name is john doe", + corrected: + "Visitor Log: John Doe checked in. NDA signature pending.", + }, + ]; + + useEffect(() => { + const timer = setTimeout(() => { + setIsVisible(true); + }, 300); + return () => clearTimeout(timer); + }, []); + + useEffect(() => { + const startDemo = () => { + // Reset all states + setIsTyping(true); + setIsSelected(false); + setShowCorrected(false); + setTypingText(""); + + // Type the text character by character - much faster + const text = demoSentences[currentSentence].original; + let i = 0; + + const typeInterval = setInterval(() => { + if (i < text.length) { + setTypingText(text.substring(0, i + 1)); + i++; + } else { + clearInterval(typeInterval); + setIsTyping(false); + + // Wait a bit, then select text + setTimeout(() => { + setIsSelected(true); + + // Wait for selection, then start refining + setTimeout(() => { + setIsAnimating(true); + + // Show refined text after processing + setTimeout(() => { + setShowCorrected(true); + setIsAnimating(false); + setIsSelected(false); + + // Wait before next cycle + setTimeout(() => { + setCurrentSentence( + (prev) => (prev + 1) % demoSentences.length, + ); + }, 2500); + }, 1800); + }, 1200); + }, 800); + } + }, 25); // Much faster typing speed + }; + + const interval = setInterval(startDemo, 8500); + startDemo(); // Start immediately + + return () => clearInterval(interval); + }, [currentSentence]); + + return ( +
+ {/* Extended background gradient with enhanced grid */} +
+ + {/* Hero Content */} +
+
+ + + System Status: Nominal + + +
+ +

+ Command Center for Modern Cultivation +

+ +

+ Real-time compliance, environmental monitoring, and task management in a single digital twin interface. +

+ +
+ + +
+ + {/* Demo Box - Made taller */} +
+
+ {/* Window-like demo box */} +
+ {/* Window header */} +
+
+
+
+
+
+
+ AI Compliance Assistant +
+
+ + {/* Content area - Made taller */} +
+
+
+ {showCorrected ? ( + + {demoSentences[currentSentence].corrected} + + ) : ( + + {typingText} + {isTyping && _} + + )} +
+ + {/* Status indicator */} +
+ {isAnimating ? ( + <> + + + Processing SOP... + + + ) : showCorrected ? ( + <> + + + Compliance Verified + + + ) : isSelected ? ( + <> + + + Analyzing Input + + + ) : ( + <> + + Awaiting Voice/Text Command... + + + )} +
+
+
+
+ + {/* Glow effect below the demo box - moved up and positioned better */} +
+
+
+
+
+ + {showPopup && ( +
+ {/* Popup content */} +
+ )} +
+ ); +}; + +export default Hero1; diff --git a/frontend/src/pages/MetrcDashboardPage.tsx b/frontend/src/pages/MetrcDashboardPage.tsx index 7a7b22f..c98e59f 100644 --- a/frontend/src/pages/MetrcDashboardPage.tsx +++ b/frontend/src/pages/MetrcDashboardPage.tsx @@ -7,6 +7,7 @@ import { } 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'; const SEVERITY_CONFIG = { CRITICAL: { color: 'text-red-600 bg-red-100 dark:bg-red-900/30', label: 'Critical' }, @@ -88,464 +89,444 @@ export default function MetrcDashboardPage() { return (
- - - -
- } - /> + - {/* Connection Status Banner */} -
-
- {connectionStatus === 'demo' ? ( - <> - -
-

Demo Mode

-

- METRC API not connected. Data shown is for demonstration purposes. -

-
- + {/* 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'} +

+
+ + + )} +
+
+ + {/* Tab Navigation */} +
+ { + [ + { id: 'overview', label: 'Overview' }, + { id: 'plants', label: 'Plant Locations' }, + { id: 'audit', label: 'Audit Trail' } + ].map(tab => ( + + )) + } +
+ + { + loading ? ( +
+ {Array.from({ length: 4 }).map((_, i) => )} +
) : ( <> - -
-

Connected to METRC

-

- Last sync: {lastSync ? lastSync.toLocaleString() : 'Never'} -

-
- - - )} -
-
- - {/* Tab Navigation */} -
- {[ - { id: 'overview', label: 'Overview' }, - { id: 'plants', label: 'Plant Locations' }, - { id: 'audit', label: 'Audit Trail' } - ].map(tab => ( - - ))} -
- - {loading ? ( -
- {Array.from({ length: 4 }).map((_, i) => )} -
- ) : ( - <> - {/* Overview Tab */} - {activeTab === 'overview' && ( -
- {/* Stats Grid */} -
-
-
-
- -
-
-

{report?.plantCount || 0}

-

Total Plants

-
-
-
- -
-
-
- -
-
-

{audit?.summary.totalMoves || 0}

-

Plant Moves (30d)

-
-
-
- -
-
-
- -
-
-

100%

-

Sync Rate

-
-
-
- -
-
-
- -
-
-

0

-

Discrepancies

-
-
-
-
- - {/* Quick Actions */} -
-

Quick Actions

-
- - - - - - - - -
-

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) => ( -
-
- + {/* Overview Tab */} + {activeTab === 'overview' && ( +
+ {/* Stats Grid */} +
+
+
+
+
-
-

{move.plantTag}

-

- {move.from} → {move.to} -

-
-
-

- {new Date(move.movedAt).toLocaleDateString()} -

+
+

{report?.plantCount || 0}

+

Total Plants

- ))} +
+ +
+
+
+ +
+
+

{audit?.summary.totalMoves || 0}

+

Plant Moves (30d)

+
+
+
+ +
+
+
+ +
+
+

100%

+

Sync Rate

+
+
+
+ +
+
+
+ +
+
+

0

+

Discrepancies

+
+
+
-
- )} -
- )} - {/* Plants Tab */} - {activeTab === 'plants' && ( -
- {/* Search */} -
-
- - setSearchTerm(e.target.value)} - className="input w-full pl-9" - /> -
-
+ {/* Quick Actions */} +
+

Quick Actions

+
+ - {/* Plant List */} - {filteredPlants.length === 0 ? ( - - ) : ( -
-
- - - - - - - - - - - - - - {filteredPlants.slice(0, 50).map((plant: any) => ( - - - - - - - - - + + + + + + +
+

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()} +

+
+
))} -
-
TagMETRC LocationRoomSectionPositionStatusActions
{plant.tagNumber}{plant.location || '-'}{plant.room}{plant.section || '-'}{plant.position || '-'} - - - Synced - - -
- - - - -
-
-
- {filteredPlants.length > 50 && ( -
- Showing 50 of {filteredPlants.length} plants +
)}
)} -
- )} - {/* Audit Tab */} - {activeTab === 'audit' && audit && ( -
- {/* Audit Summary */} -
-
+ {/* Plants Tab */} + {activeTab === 'plants' && ( +
+ {/* Search */} +
+
+ + setSearchTerm(e.target.value)} + className="input w-full pl-9" + /> +
+
+ + {/* Plant List */} + {filteredPlants.length === 0 ? ( + + ) : ( +
+
+ + + + + + + + + + + + + + {filteredPlants.slice(0, 50).map((plant: any) => ( + + + + + + + + + + ))} + +
TagMETRC LocationRoomSectionPositionStatusActions
{plant.tagNumber}{plant.location || '-'}{plant.room}{plant.section || '-'}{plant.position || '-'} + + + Synced + + +
+ + + + +
+
+
+ {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

+
+ ) : ( +
+ + + + + + + + + + + + {audit.recentMoves.map((move: any, i: number) => ( + + + + + + + + ))} + +
Plant TagFromToDateReason
{move.plantTag}{move.from}{move.to} + {new Date(move.movedAt).toLocaleDateString()} + {move.reason || '-'}
+
+ )} +
+
+ )} + + ) + } + {/* Plant History Modal */} + { + selectedHistoryPlant && ( +
+
+
-

METRC Compliance Audit

-

- {new Date(audit.dateRange.start).toLocaleDateString()} - {new Date(audit.dateRange.end).toLocaleDateString()} -

+

Plant History

+

{selectedHistoryPlant.tagNumber}

- - - Compliant - +
-
-
-

{audit.summary.totalPlants}

-

Total Plants

+
+ {/* Current Status */} +
+
+

Current Room

+

{selectedHistoryPlant.room}

+
+
+

Current Section

+

{selectedHistoryPlant.section || 'N/A'}

+
-
-

{audit.summary.totalMoves}

-

Location Changes

-
-
-

{audit.summary.uniquePlantsMoved}

-

Plants Moved

-
-
-
- {/* Recent Moves Table */} -
-
-

Location Change History

+ {/* 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) => ( +
  1. +
    + +

    + Moved to {move.to} +

    +

    + From {move.from} • Reason: {move.reason || 'Routine Maintenance'} +

    +
  2. + ))} +
+ )} +
+
+ +
+ + + Locate in 3D +
- {audit.recentMoves.length === 0 ? ( -
- -

No location changes in this period

-
- ) : ( -
- - - - - - - - - - - - {audit.recentMoves.map((move: any, i: number) => ( - - - - - - - - ))} - -
Plant TagFromToDateReason
{move.plantTag}{move.from}{move.to} - {new Date(move.movedAt).toLocaleDateString()} - {move.reason || '-'}
-
- )}
- )} - - )} - {/* Plant History Modal */} - {selectedHistoryPlant && ( -
-
-
-
-

Plant History

-

{selectedHistoryPlant.tagNumber}

-
- -
- -
- {/* 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) => ( -
  1. -
    - -

    - Moved to {move.to} -

    -

    - From {move.from} • Reason: {move.reason || 'Routine Maintenance'} -

    -
  2. - ))} -
- )} -
-
- -
- - - Locate in 3D - -
-
-
- )} -
- ); + ) + } +
+ ); }