feat/#24 WebOS Completion (#25)
#24 WebOS Completion Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: #25
This commit was merged in pull request #25.
This commit is contained in:
@@ -49,7 +49,7 @@ export const useCurrencyStore = create<CurrencyState>()(
|
||||
const { currency, option } = get();
|
||||
const { locale } = option();
|
||||
|
||||
// Base assumption: Raw numbers in mock data are in AED.
|
||||
// Base assumption: inventory and campaign amounts are stored in AED.
|
||||
let convertedAmount = amount;
|
||||
if (currency === 'USD') convertedAmount = amount * 0.272; // AED -> USD
|
||||
if (currency === 'INR') convertedAmount = amount * 25.135112; // AED -> INR (0.272 * 92.4085)
|
||||
|
||||
@@ -5,139 +5,8 @@ import type {
|
||||
AdInsight,
|
||||
LiveOptimizationEvent,
|
||||
CatalystSettings,
|
||||
LiveEventType,
|
||||
} from '@/types';
|
||||
|
||||
// ── Mock Data ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const mockCampaigns: Campaign[] = [
|
||||
{
|
||||
id: 'c1',
|
||||
name: '3BHK Prestige Launch — Dubai Marina',
|
||||
objective: 'OUTCOME_LEADS',
|
||||
status: 'ACTIVE',
|
||||
dailyBudget: 50000, // AED 500
|
||||
lifetimeSpend: 2340000,
|
||||
impressions: 487200,
|
||||
clicks: 9744,
|
||||
ctr: 2.0,
|
||||
cpa: 240,
|
||||
roi: 18.5,
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
name: 'Penthouse Whale Retarget — Instagram',
|
||||
objective: 'OUTCOME_SALES',
|
||||
status: 'ACTIVE',
|
||||
dailyBudget: 100000, // AED 1000
|
||||
lifetimeSpend: 5800000,
|
||||
impressions: 92400,
|
||||
clicks: 2772,
|
||||
ctr: 3.0,
|
||||
cpa: 2094,
|
||||
roi: 42.1,
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
name: '1BHK Investment — Lookalike Audience',
|
||||
objective: 'OUTCOME_TRAFFIC',
|
||||
status: 'PAUSED',
|
||||
dailyBudget: 25000, // AED 250
|
||||
lifetimeSpend: 980000,
|
||||
impressions: 213000,
|
||||
clicks: 4260,
|
||||
ctr: 2.0,
|
||||
cpa: 230,
|
||||
roi: 8.2,
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
const mockAssets: MarketingAsset[] = [
|
||||
{
|
||||
id: 'a1',
|
||||
name: 'Penthouse Cinematic — Sea View',
|
||||
type: 'video',
|
||||
status: 'ready',
|
||||
localUrl: '/assets/renders/penthouse_wan22_001.mp4',
|
||||
metaAssetId: 'meta_vid_83920',
|
||||
language: 'en',
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2),
|
||||
},
|
||||
{
|
||||
id: 'a2',
|
||||
name: 'Arabic Poster — 3BHK (Qwen-2512)',
|
||||
type: 'image',
|
||||
status: 'uploaded',
|
||||
localUrl: '/assets/renders/3bhk_qwen_ar.png',
|
||||
metaAssetId: 'meta_img_74811',
|
||||
language: 'ar',
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 5),
|
||||
},
|
||||
{
|
||||
id: 'a3',
|
||||
name: 'Amenity Deck Reel — Wan 2.2 14B',
|
||||
type: 'video',
|
||||
status: 'rendering',
|
||||
renderMessage: 'Wan 2.2 is compositing the infinity pool reflection...',
|
||||
language: 'en',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'a4',
|
||||
name: 'English Poster — Penthouse Launch',
|
||||
type: 'image',
|
||||
status: 'queued',
|
||||
renderMessage: 'Qwen-Image 2512 queued for cinematic poster render...',
|
||||
language: 'en',
|
||||
createdAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
const mockInsights: AdInsight[] = Array.from({ length: 14 }, (_, i) => {
|
||||
const d = new Date(Date.now() - 1000 * 60 * 60 * 24 * (13 - i));
|
||||
return {
|
||||
adSetId: `as_${i}`,
|
||||
adSetName: i % 2 === 0 ? '3BHK — Dubai Marina' : 'Penthouse Retarget',
|
||||
spend: 800 + Math.floor(Math.random() * 400),
|
||||
impressions: 18000 + Math.floor(Math.random() * 10000),
|
||||
clicks: 360 + Math.floor(Math.random() * 200),
|
||||
ctr: 1.8 + Math.random() * 1.5,
|
||||
cpa: 190 + Math.floor(Math.random() * 120),
|
||||
roi: 14 + Math.random() * 20,
|
||||
date: d.toISOString().split('T')[0],
|
||||
};
|
||||
});
|
||||
|
||||
function makeLiveEvent(
|
||||
type: LiveEventType,
|
||||
message: string,
|
||||
campaignName?: string,
|
||||
value?: string
|
||||
): LiveOptimizationEvent {
|
||||
return {
|
||||
id: `ev_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
|
||||
type,
|
||||
message,
|
||||
campaignName,
|
||||
timestamp: new Date(),
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
const mockLiveEvents: LiveOptimizationEvent[] = [
|
||||
makeLiveEvent('pause', 'Paused Ad Set B due to CPA exceeding AED 500 threshold.', '3BHK Prestige Launch', 'CPA: AED 512'),
|
||||
makeLiveEvent('shift', 'Shifted AED 200/day budget from Ad Set B to Ad Set A (lower CPA).', '3BHK Prestige Launch', '+AED 200'),
|
||||
makeLiveEvent('rotate', 'Rotated in Penthouse Cinematic (sea view) as new creative for A/B test.', 'Penthouse Whale Retarget'),
|
||||
makeLiveEvent('optimize', 'Expanded Lookalike Audience to 2% similarity — 48k new reach.', '1BHK Investment', '+48k reach'),
|
||||
makeLiveEvent('create', 'Created new Ad Set targeting High-Net-Worth Lookalike from 23 new CRM Closed/Won leads.', 'Penthouse Whale Retarget'),
|
||||
];
|
||||
|
||||
// ── Store Types ────────────────────────────────────────────────────────────────
|
||||
|
||||
interface MarketingState {
|
||||
@@ -162,10 +31,10 @@ interface MarketingState {
|
||||
// ── Store ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const useMarketingStore = create<MarketingState>()((set) => ({
|
||||
campaigns: mockCampaigns,
|
||||
activeAssets: mockAssets,
|
||||
adInsights: mockInsights,
|
||||
liveEvents: mockLiveEvents,
|
||||
campaigns: [],
|
||||
activeAssets: [],
|
||||
adInsights: [],
|
||||
liveEvents: [],
|
||||
activeTab: 'studio',
|
||||
|
||||
settings: {
|
||||
|
||||
@@ -61,6 +61,7 @@ interface DashboardState {
|
||||
metrics: DashboardMetrics;
|
||||
velocityData: LeadVelocityData[];
|
||||
updateMetrics: (metrics: Partial<DashboardMetrics>) => void;
|
||||
setVelocityData: (data: LeadVelocityData[]) => void;
|
||||
addVelocityDataPoint: (data: LeadVelocityData) => void;
|
||||
}
|
||||
|
||||
@@ -69,6 +70,7 @@ interface InventoryState {
|
||||
units: Unit[];
|
||||
selectedUnitId: string | null;
|
||||
filterStatus: Unit['status'] | 'all';
|
||||
setUnits: (units: Unit[]) => void;
|
||||
setSelectedUnit: (unitId: string | null) => void;
|
||||
setFilterStatus: (status: Unit['status'] | 'all') => void;
|
||||
}
|
||||
@@ -101,160 +103,6 @@ interface StoreState extends
|
||||
SystemState,
|
||||
NotificationState { }
|
||||
|
||||
|
||||
// Mock Data
|
||||
const mockLeads: Lead[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Mohammed Al-Rashid',
|
||||
phone: '+971 55 123 4567',
|
||||
source: 'whatsapp',
|
||||
status: 'hot',
|
||||
lastMessage: 'Can we schedule a viewing for the penthouse tomorrow?',
|
||||
lastActive: new Date(Date.now() - 1000 * 60 * 5),
|
||||
unreadCount: 2,
|
||||
qualification: 'whale',
|
||||
budget: 'AED 15M+',
|
||||
interest: 'Penthouse Suite',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Sarah Chen',
|
||||
phone: '+971 50 987 6543',
|
||||
source: 'walkin',
|
||||
status: 'engaged',
|
||||
lastMessage: 'Thank you for the brochure. I will review with my partner.',
|
||||
lastActive: new Date(Date.now() - 1000 * 60 * 30),
|
||||
unreadCount: 0,
|
||||
qualification: 'potential',
|
||||
budget: 'AED 5-8M',
|
||||
interest: '2 Bedroom Sea View',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'James Wilson',
|
||||
phone: '+971 52 456 7890',
|
||||
source: 'website',
|
||||
status: 'new',
|
||||
lastMessage: 'Interested in investment opportunities.',
|
||||
lastActive: new Date(Date.now() - 1000 * 60 * 60 * 2),
|
||||
unreadCount: 1,
|
||||
qualification: 'potential',
|
||||
budget: 'AED 3-5M',
|
||||
interest: '1 Bedroom Investment',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Fatima Hassan',
|
||||
phone: '+971 54 321 0987',
|
||||
source: 'whatsapp',
|
||||
status: 'qualified',
|
||||
lastMessage: 'What are the payment plan options?',
|
||||
lastActive: new Date(Date.now() - 1000 * 60 * 60 * 4),
|
||||
unreadCount: 0,
|
||||
qualification: 'whale',
|
||||
budget: 'AED 12M+',
|
||||
interest: '3 Bedroom + Maid',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'David Kumar',
|
||||
phone: '+971 56 789 0123',
|
||||
source: 'walkin',
|
||||
status: 'closed',
|
||||
lastMessage: 'Contract signed. Thank you!',
|
||||
lastActive: new Date(Date.now() - 1000 * 60 * 60 * 24),
|
||||
unreadCount: 0,
|
||||
qualification: 'whale',
|
||||
budget: 'AED 20M',
|
||||
interest: 'Full Floor',
|
||||
},
|
||||
];
|
||||
|
||||
const mockMessages: Record<string, ChatMessage[]> = {
|
||||
'1': [
|
||||
{
|
||||
id: 'm1',
|
||||
sender: 'user',
|
||||
content: 'Hi, I am interested in the penthouse units.',
|
||||
timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2),
|
||||
},
|
||||
{
|
||||
id: 'm2',
|
||||
sender: 'oracle',
|
||||
content: 'Welcome! Our penthouse collection features 4 exclusive units with panoramic sea views. Prices start at AED 15M. Would you like to know more about specific floor plans?',
|
||||
timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2 + 1000 * 30),
|
||||
},
|
||||
{
|
||||
id: 'm3',
|
||||
sender: 'user',
|
||||
content: 'Can we schedule a viewing for the penthouse tomorrow?',
|
||||
timestamp: new Date(Date.now() - 1000 * 60 * 5),
|
||||
},
|
||||
],
|
||||
'2': [
|
||||
{
|
||||
id: 'm4',
|
||||
sender: 'oracle',
|
||||
content: 'Hello Sarah! Thank you for visiting our Experience Center today. Here is the digital brochure for the 2-bedroom units we discussed.',
|
||||
timestamp: new Date(Date.now() - 1000 * 60 * 60 * 4),
|
||||
},
|
||||
{
|
||||
id: 'm5',
|
||||
sender: 'user',
|
||||
content: 'Thank you for the brochure. I will review with my partner.',
|
||||
timestamp: new Date(Date.now() - 1000 * 60 * 30),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockVisitors: Visitor[] = [
|
||||
{
|
||||
id: 'v1',
|
||||
faceId: 'face_001',
|
||||
sentiment: 'excited',
|
||||
confidence: 0.92,
|
||||
dwellTime: 450,
|
||||
zone: 'Penthouse Showroom',
|
||||
timestamp: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'v2',
|
||||
faceId: 'face_002',
|
||||
sentiment: 'interested',
|
||||
confidence: 0.87,
|
||||
dwellTime: 320,
|
||||
zone: 'Amenity Deck VR',
|
||||
timestamp: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'v3',
|
||||
faceId: 'face_003',
|
||||
sentiment: 'neutral',
|
||||
confidence: 0.78,
|
||||
dwellTime: 180,
|
||||
zone: 'Reception',
|
||||
timestamp: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
const mockVelocityData: LeadVelocityData[] = Array.from({ length: 12 }, (_, i) => ({
|
||||
time: `${9 + Math.floor(i / 2)}:${i % 2 === 0 ? '00' : '30'}`,
|
||||
generated: Math.floor(Math.random() * 8) + 2,
|
||||
closed: Math.floor(Math.random() * 3),
|
||||
}));
|
||||
|
||||
const mockUnits: Unit[] = [
|
||||
{ id: 'u1', unitNumber: 'PH-01', type: 'penthouse', floor: 45, area: 520, price: 25000000, status: 'available', view: 'Panoramic Sea', lastUpdated: new Date() },
|
||||
{ id: 'u2', unitNumber: 'PH-02', type: 'penthouse', floor: 45, area: 480, price: 22000000, status: 'reserved', view: 'Sea & Marina', lastUpdated: new Date() },
|
||||
{ id: 'u3', unitNumber: '4501', type: '3br', floor: 45, area: 280, price: 12000000, status: 'available', view: 'Sea View', lastUpdated: new Date() },
|
||||
{ id: 'u4', unitNumber: '4502', type: '3br', floor: 45, area: 265, price: 11500000, status: 'sold', view: 'Marina View', lastUpdated: new Date() },
|
||||
{ id: 'u5', unitNumber: '4401', type: '2br', floor: 44, area: 180, price: 7500000, status: 'available', view: 'Sea View', lastUpdated: new Date() },
|
||||
{ id: 'u6', unitNumber: '4402', type: '2br', floor: 44, area: 175, price: 7200000, status: 'hold', view: 'City View', lastUpdated: new Date() },
|
||||
{ id: 'u7', unitNumber: '4301', type: '1br', floor: 43, area: 95, price: 4200000, status: 'available', view: 'Sea View', lastUpdated: new Date() },
|
||||
{ id: 'u8', unitNumber: '4302', type: '1br', floor: 43, area: 92, price: 4000000, status: 'available', view: 'City View', lastUpdated: new Date() },
|
||||
];
|
||||
|
||||
export const useStore = create<StoreState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
@@ -272,9 +120,9 @@ export const useStore = create<StoreState>()(
|
||||
setSidebarExpanded: (expanded) => set({ sidebarExpanded: expanded }),
|
||||
|
||||
// Oracle State
|
||||
leads: mockLeads,
|
||||
leads: [],
|
||||
activeLeadId: null,
|
||||
messages: mockMessages,
|
||||
messages: {},
|
||||
isOracleThinking: false,
|
||||
setLeads: (leads) => set({ leads }),
|
||||
replaceMessages: (messages) => set({ messages }),
|
||||
@@ -293,7 +141,7 @@ export const useStore = create<StoreState>()(
|
||||
})),
|
||||
|
||||
// Sentinel State
|
||||
visitors: mockVisitors,
|
||||
visitors: [],
|
||||
isAlertActive: false,
|
||||
alertMessage: '',
|
||||
addVisitor: (visitor) => set((state) => ({
|
||||
@@ -307,37 +155,39 @@ export const useStore = create<StoreState>()(
|
||||
|
||||
// Dashboard State
|
||||
metrics: {
|
||||
activeVisitors: 12,
|
||||
todayLeads: 24,
|
||||
closedDeals: 3,
|
||||
conversionRate: 12.5,
|
||||
sentiment: 78,
|
||||
activeVisitors: 0,
|
||||
todayLeads: 0,
|
||||
closedDeals: 0,
|
||||
conversionRate: 0,
|
||||
sentiment: 0,
|
||||
systemHealth: {
|
||||
cpu: 34,
|
||||
gpu: 28,
|
||||
memory: 42,
|
||||
temperature: 58,
|
||||
cpu: 0,
|
||||
gpu: 0,
|
||||
memory: 0,
|
||||
temperature: 0,
|
||||
},
|
||||
},
|
||||
velocityData: mockVelocityData,
|
||||
velocityData: [],
|
||||
updateMetrics: (metrics) => set((state) => ({
|
||||
metrics: { ...state.metrics, ...metrics },
|
||||
})),
|
||||
setVelocityData: (data) => set({ velocityData: data }),
|
||||
addVelocityDataPoint: (data) => set((state) => ({
|
||||
velocityData: [...state.velocityData.slice(1), data],
|
||||
})),
|
||||
|
||||
// Inventory State
|
||||
units: mockUnits,
|
||||
units: [],
|
||||
selectedUnitId: null,
|
||||
filterStatus: 'all',
|
||||
setUnits: (units) => set({ units }),
|
||||
setSelectedUnit: (unitId) => set({ selectedUnitId: unitId }),
|
||||
setFilterStatus: (status) => set({ filterStatus: status }),
|
||||
|
||||
// System State
|
||||
status: {
|
||||
isConnected: true,
|
||||
serverStatus: 'online',
|
||||
isConnected: false,
|
||||
serverStatus: 'syncing',
|
||||
lastSync: new Date(),
|
||||
version: '2.1.0',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user