feat(ui): complete ProCanvas role-based overhaul and fix Recharts crash

- Implement unified Neomorphic tactical layout for ProCanvas across Agent, Admin, and Owner roles.

- Add Quick Operations Log widget for immediate action logging.

- Integrate discrete Checkpoint/Stages progression system to replace static prize tracks.

- Build custom AnimatedCounter for GSAP-style numerical state changes.

- Fix Recharts ResponsiveContainer loading crash in OwnerSnapshot.jsx.
This commit is contained in:
Satyam
2026-02-21 21:23:37 +05:30
parent 7e166726da
commit 058b5bb217
8 changed files with 741 additions and 28 deletions
+128
View File
@@ -0,0 +1,128 @@
import { useMemo } from 'react';
const LEVEL_THRESHOLDS = [
{ level: 1, xp: 0, title: 'Rookie' },
{ level: 2, xp: 1000, title: 'Scout' },
{ level: 3, xp: 2500, title: 'Trekker' },
{ level: 4, xp: 5000, title: 'Pathfinder' },
{ level: 5, xp: 10000, title: 'Roof Warrior' },
{ level: 6, xp: 15000, title: 'Storm Chaser' },
{ level: 7, xp: 25000, title: 'Elite Operator' },
{ level: 8, xp: 40000, title: 'Neighborhood Boss' },
{ level: 9, xp: 60000, title: 'Commanding Officer' },
{ level: 10, xp: 100000, title: 'Legend' }
];
export const useGamification = (user) => {
return useMemo(() => {
if (!user) return null;
const xp = user.xp || 0;
// Calculate Level
let currentLevelObj = LEVEL_THRESHOLDS[0];
let nextLevelObj = LEVEL_THRESHOLDS[1];
for (let i = 0; i < LEVEL_THRESHOLDS.length; i++) {
if (xp >= LEVEL_THRESHOLDS[i].xp) {
currentLevelObj = LEVEL_THRESHOLDS[i];
nextLevelObj = LEVEL_THRESHOLDS[i + 1] || LEVEL_THRESHOLDS[i]; // Max level fallback
} else {
break;
}
}
const { level, title } = currentLevelObj;
// XP Progress
let xpProgress = 100;
let xpToNext = 0;
let nextLevelXp = nextLevelObj.xp;
let currentLevelBaseXp = currentLevelObj.xp;
if (level < LEVEL_THRESHOLDS[LEVEL_THRESHOLDS.length - 1].level) {
const range = nextLevelXp - currentLevelBaseXp;
const progress = xp - currentLevelBaseXp;
xpProgress = Math.min(100, Math.max(0, (progress / range) * 100));
xpToNext = nextLevelXp - xp;
}
// --- PHASE 3: CHECKPOINTS & STAGES ---
const STAGES = [
{
id: 1,
title: "Stage 1: Groundwork",
requirement: { type: "doorsKnocked", amount: 20 },
reward: { type: "coupon", name: "$25 Uber Eats Card", icon: "uber" },
progress: Math.min((user.doorsKnocked || 0), 20),
goal: 20
},
{
id: 2,
title: "Stage 2: Connections",
requirement: { type: "appointmentsSet", amount: 10 },
reward: { type: "giftcard", name: "$50 Amazon Card", icon: "amazon" },
progress: Math.min((user.appointmentsSet || 0), 10),
goal: 10
},
{
id: 3,
title: "Stage 3: Rainmaker",
requirement: { type: "leadsGained", amount: 25 },
reward: { type: "cash", name: "$150 Performance Bonus", icon: "cash" },
progress: Math.min((user.leadsGained || 0), 25),
goal: 25
}
];
// Determine lock states
const stages = STAGES.map((s, idx) => {
let status = "locked";
if (s.progress >= s.goal) {
status = "completed";
} else if (idx === 0 || STAGES[idx - 1].progress >= STAGES[idx - 1].goal) {
status = "in-progress";
}
return { ...s, status };
});
const activeStage = stages.find(s => s.status === "in-progress") || stages[stages.length - 1];
// --- PHASE 3: DETAILED BADGE ARCHITECTURE ---
// Mapping known string arrays from mockStore into detailed objects
const BADGE_DEFINITIONS = [
{ id: 'Hot Spot Hunter', title: "Hot Spot Hunter", desc: "Identify 10 high-damage zones.", criteria: "10 Hot Leads identified." },
{ id: 'Storm Chaser', title: "Storm Chaser", desc: "Canvass a territory within 48h of a storm.", criteria: "1 Storm deployed operation." },
{ id: 'Star Closer', title: "Star Closer", desc: "Maintain a 50% conversion rate.", criteria: "Convert 5 deals continuously." },
{ id: 'Map Master', title: "Map Master", desc: "Knock 500 total doors.", criteria: "500 Lifetime Doors." },
{ id: 'First Knock', title: "First Knock", desc: "Complete your first day in the field.", criteria: "Log your first shift." }
];
const badges = BADGE_DEFINITIONS.map(def => {
const isUnlocked = (user.achievements || []).includes(def.id);
return { ...def, status: isUnlocked ? 'unlocked' : 'locked' };
});
// Derived Stats
const stats = {
doorsKnocked: user.doorsKnocked || 0,
leadsGained: user.leadsGained || 0,
appointmentsSet: user.appointmentsSet || 0,
streakDays: user.streakDays || 0,
};
return {
level,
title,
currentXp: xp,
nextLevelXp,
xpProgress,
xpToNext,
stats,
badges,
stages,
activeStage,
achievements: user.achievements || [] // keeping for legacy
};
}, [user]);
};