diff --git a/src/hooks/use-campaigns.ts b/src/hooks/use-campaigns.ts new file mode 100644 index 0000000..2be4ec8 --- /dev/null +++ b/src/hooks/use-campaigns.ts @@ -0,0 +1,37 @@ +import { useMemo } from 'react'; + +import type { Campaign, Ad, CampaignStatus } from '@/types/entities'; +import { useData } from '@/providers/data-provider'; + +type UseCampaignsFilters = { + status?: CampaignStatus; +}; + +type UseCampaignsResult = { + campaigns: Campaign[]; + ads: Ad[]; +}; + +export const useCampaigns = (filters: UseCampaignsFilters = {}): UseCampaignsResult => { + const { campaigns, ads } = useData(); + const { status } = filters; + + const filteredCampaigns = useMemo(() => { + if (status === undefined) { + return campaigns; + } + + return campaigns.filter((campaign) => campaign.campaignStatus === status); + }, [campaigns, status]); + + const filteredAds = useMemo(() => { + const campaignIds = new Set(filteredCampaigns.map((campaign) => campaign.id)); + + return ads.filter((ad) => ad.campaignId !== null && campaignIds.has(ad.campaignId)); + }, [ads, filteredCampaigns]); + + return { + campaigns: filteredCampaigns, + ads: filteredAds, + }; +}; diff --git a/src/hooks/use-follow-ups.ts b/src/hooks/use-follow-ups.ts new file mode 100644 index 0000000..7d8e7ef --- /dev/null +++ b/src/hooks/use-follow-ups.ts @@ -0,0 +1,42 @@ +import { useMemo } from 'react'; + +import type { FollowUp } from '@/types/entities'; +import { useData } from '@/providers/data-provider'; + +type UseFollowUpsResult = { + followUps: FollowUp[]; + overdue: FollowUp[]; + upcoming: FollowUp[]; +}; + +export const useFollowUps = (): UseFollowUpsResult => { + const { followUps } = useData(); + + const now = useMemo(() => new Date(), []); + + const overdue = useMemo(() => { + return followUps.filter((followUp) => { + if (followUp.followUpStatus === 'OVERDUE') { + return true; + } + + if (followUp.followUpStatus === 'PENDING' && followUp.scheduledAt !== null) { + return new Date(followUp.scheduledAt) < now; + } + + return false; + }); + }, [followUps, now]); + + const upcoming = useMemo(() => { + return followUps.filter((followUp) => { + return followUp.followUpStatus === 'PENDING' && followUp.scheduledAt !== null && new Date(followUp.scheduledAt) >= now; + }); + }, [followUps, now]); + + return { + followUps, + overdue, + upcoming, + }; +}; diff --git a/src/hooks/use-leads.ts b/src/hooks/use-leads.ts new file mode 100644 index 0000000..8a1c441 --- /dev/null +++ b/src/hooks/use-leads.ts @@ -0,0 +1,56 @@ +import { useMemo } from 'react'; + +import type { Lead, LeadSource, LeadStatus } from '@/types/entities'; +import { useData } from '@/providers/data-provider'; + +type UseLeadsFilters = { + source?: LeadSource; + status?: LeadStatus; + search?: string; +}; + +type UseLeadsResult = { + leads: Lead[]; + total: number; + updateLead: (id: string, updates: Partial) => void; +}; + +export const useLeads = (filters: UseLeadsFilters = {}): UseLeadsResult => { + const { leads, updateLead } = useData(); + const { source, status, search } = filters; + + const filteredLeads = useMemo(() => { + return leads.filter((lead) => { + if (source !== undefined && lead.leadSource !== source) { + return false; + } + + if (status !== undefined && lead.leadStatus !== status) { + return false; + } + + if (search !== undefined && search.trim() !== '') { + const query = search.trim().toLowerCase(); + const firstName = lead.contactName?.firstName?.toLowerCase() ?? ''; + const lastName = lead.contactName?.lastName?.toLowerCase() ?? ''; + const fullName = `${firstName} ${lastName}`.trim(); + const phones = (lead.contactPhone ?? []).map((p) => p.number.toLowerCase()); + + const matchesName = firstName.includes(query) || lastName.includes(query) || fullName.includes(query); + const matchesPhone = phones.some((phone) => phone.includes(query)); + + if (!matchesName && !matchesPhone) { + return false; + } + } + + return true; + }); + }, [leads, source, status, search]); + + return { + leads: filteredLeads, + total: filteredLeads.length, + updateLead, + }; +}; diff --git a/src/providers/auth-provider.tsx b/src/providers/auth-provider.tsx new file mode 100644 index 0000000..fd01ba0 --- /dev/null +++ b/src/providers/auth-provider.tsx @@ -0,0 +1,68 @@ +import type { ReactNode } from 'react'; +import { createContext, useContext, useState } from 'react'; + +export type Role = 'executive' | 'admin'; + +type User = { + name: string; + initials: string; + role: Role; + email: string; +}; + +type AuthContextType = { + user: User; + setRole: (role: Role) => void; + isAdmin: boolean; + isAuthenticated: boolean; + login: () => void; + logout: () => void; +}; + +const USERS: Record = { + admin: { + name: 'Admin User', + initials: 'AU', + role: 'admin', + email: 'admin@ramaiah.com', + }, + executive: { + name: 'Sanjay M.', + initials: 'SM', + role: 'executive', + email: 'sanjay@ramaiah.com', + }, +}; + +const AuthContext = createContext(undefined); + +export const useAuth = (): AuthContextType => { + const context = useContext(AuthContext); + + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + + return context; +}; + +interface AuthProviderProps { + children: ReactNode; +} + +export const AuthProvider = ({ children }: AuthProviderProps) => { + const [role, setRole] = useState('executive'); + const [isAuthenticated, setIsAuthenticated] = useState(false); + + const user = USERS[role]; + const isAdmin = role === 'admin'; + + const login = () => setIsAuthenticated(true); + const logout = () => setIsAuthenticated(false); + + return ( + + {children} + + ); +}; diff --git a/src/providers/data-provider.tsx b/src/providers/data-provider.tsx new file mode 100644 index 0000000..dc98930 --- /dev/null +++ b/src/providers/data-provider.tsx @@ -0,0 +1,52 @@ +import type { ReactNode } from 'react'; +import { createContext, useContext, useState } from 'react'; + +import type { Lead, Campaign, Ad, LeadActivity, FollowUp, WhatsAppTemplate, Agent } from '@/types/entities'; +import { mockLeads, mockCampaigns, mockAds, mockFollowUps, mockLeadActivities, mockTemplates, mockAgents } from '@/mocks'; + +type DataContextType = { + leads: Lead[]; + campaigns: Campaign[]; + ads: Ad[]; + followUps: FollowUp[]; + leadActivities: LeadActivity[]; + templates: WhatsAppTemplate[]; + agents: Agent[]; + updateLead: (id: string, updates: Partial) => void; +}; + +const DataContext = createContext(undefined); + +export const useData = (): DataContextType => { + const context = useContext(DataContext); + + if (context === undefined) { + throw new Error('useData must be used within a DataProvider'); + } + + return context; +}; + +interface DataProviderProps { + children: ReactNode; +} + +export const DataProvider = ({ children }: DataProviderProps) => { + const [leads, setLeads] = useState(mockLeads); + const [campaigns] = useState(mockCampaigns); + const [ads] = useState(mockAds); + const [followUps] = useState(mockFollowUps); + const [leadActivities] = useState(mockLeadActivities); + const [templates] = useState(mockTemplates); + const [agents] = useState(mockAgents); + + const updateLead = (id: string, updates: Partial) => { + setLeads((prev) => prev.map((lead) => (lead.id === id ? { ...lead, ...updates } : lead))); + }; + + return ( + + {children} + + ); +};