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:
+147
-3
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user