feat(crm): Complete Admin Action Center & Reliability Overhaul

- Added ActionCenterWidget, DetailDrawer, AgentSelectionModal
- Implemented global ErrorBoundary and Chatbot resilience
- Updated documentation (README, Structure, Status, Overview)
- Refined responsive design and modal portals
This commit is contained in:
Satyam
2026-02-11 18:33:04 +05:30
parent bfc90b668b
commit be828b205d
7 changed files with 676 additions and 12 deletions
+147 -3
View File
@@ -1,4 +1,6 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { toast } from 'sonner';
import { logger } from '../utils/logger';
// --- DATA CONSTANTS (Extracted from old App.jsx) ---
@@ -294,6 +296,15 @@ function generateProperties() {
// Placeholder status, will be overwritten
canvassingStatus: "Neutral",
photos: isCommercial ? ["https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=800", "https://images.unsplash.com/photo-1542665952-14513db15293?w=800"] : style.photos,
// --- NEW FIELDS FOR ADMIN ACTION CENTER ---
assignedAgentId: null, // Default unassigned
lastContactDate: null,
pendingSignature: false,
proposalSentDate: null,
proposalValue: 0,
// ------------------------------------------
propertyData: {
propertyId: `P-${2600 + index}`,
propertyAddress: loc.address,
@@ -430,7 +441,83 @@ function generateProperties() {
}
});
// --- INJECT REQUIRED MOCK DATA FOR ACTION CENTER ---
// A. Unassigned Leads (assignedAgentId: null, canvassingStatus: "Lead")
// Target addresses: 2600 Dunwick Dr, 3900 Arizona Pl, 2112 Winslow Dr, Dallas/Plano Marriott, Legacy Town Center
const unassignedTargets = [
"2600 Dunwick Dr, Plano, TX 75023",
"3900 Arizona Pl, Plano, TX 75023, USA", // Note: original data has USA
"2112 Winslow Dr, Plano, TX 75023",
"Dallas/Plano Marriott at Legacy Town Center, 7121 Bishop Rd, Plano, TX 75024, United States",
"Legacy Town Center Plano, 5760 Daniel Rd, Plano, TX 75024, United States"
];
const unassignedValues = {
"2600 Dunwick Dr, Plano, TX 75023": 53173,
"3900 Arizona Pl, Plano, TX 75023, USA": 11608,
"2112 Winslow Dr, Plano, TX 75023": 40669,
"Dallas/Plano Marriott at Legacy Town Center, 7121 Bishop Rd, Plano, TX 75024, United States": 15766,
"Legacy Town Center Plano, 5760 Daniel Rd, Plano, TX 75024, United States": 12792
};
allProperties.forEach(p => {
const addr = p.propertyData.propertyAddress;
if (unassignedTargets.includes(addr)) {
p.canvassingStatus = "Lead"; // "Lead" = Unassigned basically in this context, or just "Neutral" but we track assignments
p.assignedAgentId = null;
p.propertyData.currentEstimatedMarketValue = unassignedValues[addr];
} else {
// Default random assignment for others if not unassigned
if (p.canvassingStatus !== "Hot Lead") { // Keep Hot Leads potentially unassigned or specific
// p.assignedAgentId = "e1"; // Optional: assign everything else
}
}
});
// B. Hot Leads Not Contacted
// Data: 2629 Rothland Ln (2026-02-08), 3920 Arizona Pl (2026-02-08), 3912 Arizona Pl (Never), 3909 Sailmaker Ln (Never)
const hotLeadTargets = {
"2629 Rothland Ln, Plano, TX 75023": "2026-02-08T10:00:00Z",
"3920 Arizona Pl, Plano, TX 75023, USA": "2026-02-08T14:30:00Z",
"3912 Arizona Pl, Plano, TX 75023, USA": null,
"3909 Sailmaker Ln, Plano, TX 75023, USA": null
};
allProperties.forEach(p => {
if (hotLeadTargets.hasOwnProperty(p.propertyData.propertyAddress)) {
p.canvassingStatus = "Hot Lead";
p.lastContactDate = hotLeadTargets[p.propertyData.propertyAddress];
// Set values if needed match User Data?
// The prompt gave values: 2629 ($20,298), 3920 ($84,288), 3912 ($73,307), 3909 ($72,563)
if (p.propertyData.propertyAddress.includes("2629 Rothland")) p.propertyData.currentEstimatedMarketValue = 20298;
if (p.propertyData.propertyAddress.includes("3920 Arizona")) p.propertyData.currentEstimatedMarketValue = 84288;
if (p.propertyData.propertyAddress.includes("3912 Arizona")) p.propertyData.currentEstimatedMarketValue = 73307;
if (p.propertyData.propertyAddress.includes("3909 Sailmaker")) p.propertyData.currentEstimatedMarketValue = 72563;
}
});
// C. Pending Signatures
// Data: Client 1 (2612 Dunwick), Client 2 (2608 Dunwick), Client 9 (2617 Rothland), Client 12 (6609 Phoenix)
const signatureTargets = {
"2612 Dunwick Dr, Plano, TX 75023": { date: "2026-02-05", val: 8521 },
"2608 Dunwick Dr, Plano, TX 75023": { date: "2026-02-09", val: 16468 },
"2617 Rothland Ln, Plano, TX 75023": { date: "2026-02-08", val: 18392 },
"6609 Phoenix Pl, Plano, TX 75023, USA": { date: "2026-02-06", val: 31768 }
};
allProperties.forEach(p => {
if (signatureTargets.hasOwnProperty(p.propertyData.propertyAddress)) {
p.pendingSignature = true;
p.proposalSentDate = signatureTargets[p.propertyData.propertyAddress].date;
p.proposalValue = signatureTargets[p.propertyData.propertyAddress].val;
p.propertyData.pendingSignature = true; // Sync with inner object if needed, but we extended outer
} else {
p.pendingSignature = false;
}
});
// -------------------------------------------------------
// 3. Assign Tenants (Strict Counts & Data Depth)
// User Request: 10-15 Total Rented. 5-8 with "Complete" info, rest "Sparse" (Mandatory only).
@@ -536,8 +623,6 @@ function generateProperties() {
// --- NEW DATA (Users & Meetings) ---
// --- NEW DATA (Users & Meetings) ---
const MOCK_USERS = [
// Customers
{ id: 'c1', type: 'customer', username: 'alice', email: 'alice@example.com', password: 'password', name: 'Alice Customer', propertyId: 'P-2600' },
@@ -706,6 +791,61 @@ export const MockStoreProvider = ({ children }) => {
setMeetings(prev => [...prev, { ...meeting, id: `m${prev.length + 1}` }]);
};
// --- NEW ACTIONS for ADMIN CENTER ---
const assignAgent = (propertyId, agentId) => {
let found = false;
setProperties(prev => prev.map(p => {
if (p.id === propertyId || p.propertyData.propertyId === propertyId) {
found = true;
return { ...p, assignedAgentId: agentId, canvassingStatus: "Assigned" };
}
return p;
}));
if (!found) {
toast.error("Failed to assign agent: Property not found.");
return false;
}
const agent = users.find(u => u.id === agentId);
toast.success(`Agent ${agent ? agent.name : 'assigned'} linked to property.`);
logger.info(`Property ${propertyId} assigned to Agent ${agentId}`);
return true;
};
const markLeadContacted = (propertyId) => {
let found = false;
setProperties(prev => prev.map(p => {
if (p.id === propertyId || p.propertyData.propertyId === propertyId) {
found = true;
return { ...p, lastContactDate: new Date().toISOString() };
}
return p;
}));
if (!found) {
toast.error("Action failed: Property not found.");
return false;
}
toast.success("Lead marked as contacted.");
return true;
};
const sendReminder = (propertyId) => {
const prop = properties.find(p => p.id === propertyId || p.propertyData.propertyId === propertyId);
if (!prop) {
toast.error("Failed to send reminder: Client not found.");
return false;
}
const name = prop?.ownerData?.fullName || "Client";
toast.success(`Reminder sent to ${name}`);
logger.info(`Reminder sent to property ${propertyId}`);
return true;
};
return (
<MockStoreContext.Provider value={{
properties,
@@ -716,7 +856,11 @@ export const MockStoreProvider = ({ children }) => {
addMeeting,
updateUser: (updatedUser) => {
setUsers(prev => prev.map(u => u.id === updatedUser.id ? updatedUser : u));
}
},
// Export new actions
assignAgent,
markLeadContacted,
sendReminder
}}>
{children}
</MockStoreContext.Provider>