diff --git a/mOOm b/mOOm new file mode 100644 index 00000000000..abbd07c321f --- /dev/null +++ b/mOOm @@ -0,0 +1,545 @@ +import React, { useState, useMemo } from "react"; + +// Moom CRM - Single-file React App (Tailwind CSS required in host project) +// Default export: MoomCRMApp +// Features: Role switcher (Admin / Team Lead / Associate), Dashboard, Leads List, Lead Detail, Teams, Users +// Data: in-memory sample data. Replace with API calls in real app. + +export default function MoomCRMApp() { + const [role, setRole] = useState("admin"); // 'admin' | 'team_lead' | 'associate' + const [selectedPage, setSelectedPage] = useState("dashboard"); + const [selectedLead, setSelectedLead] = useState(null); + + // sample teams and users + const [teams, setTeams] = useState([ + { id: "t1", name: "Lucknow Team", managerId: "u2", region: "Lucknow" }, + { id: "t2", name: "Ayodhya Team", managerId: "u3", region: "Ayodhya" }, + ]); + + const [users, setUsers] = useState([ + { id: "u1", name: "Admin User", email: "admin@moom.com", role: "admin", teamId: null }, + { id: "u2", name: "Suresh Yadav", email: "suresh.associate@moom.com", role: "team_lead", teamId: "t1" }, + { id: "u3", name: "Anita Sharma", email: "anita.employee@moom.com", role: "team_lead", teamId: "t2" }, + { id: "u4", name: "Ramesh Kumar", email: "ramesh@moom.com", role: "associate", teamId: "t1" }, + ]); + + const [leads, setLeads] = useState([ + { + id: "L-1001", + title: "Maa Kisan Traders - Fertilizer Needs", + company: "Maa Kisan Traders", + contact: "Ramesh Kumar", + phone: "+919812345678", + ownerId: "u4", + teamId: "t1", + stage: "New", + expectedValue: 15000, + source: "Website", + territory: "Lucknow", + createdAt: "2025-09-28", + notes: "Interested in bulk fertilizer supply", + }, + { + id: "L-1002", + title: "Krishna Agro Pvt Ltd - Seeds", + company: "Krishna Agro Pvt Ltd", + contact: "Anita Sharma", + phone: "+919876543219", + ownerId: "u3", + teamId: "t2", + stage: "Contacted", + expectedValue: 25000, + source: "Referral", + territory: "Ayodhya", + createdAt: "2025-09-25", + notes: "Looking for organic seeds", + }, + ]); + + // derived data + const me = useMemo(() => users.find((u) => (role === "admin" ? u.id === "u1" : u.role === role) ?? users[0]), [role, users]); + + // role-based filtered leads + const visibleLeads = useMemo(() => { + if (role === "admin") return leads; + if (role === "team_lead") { + // team lead sees leads for their team + const myUser = users.find((u) => u.role === "team_lead"); + if (!myUser) return []; + return leads.filter((l) => l.teamId === myUser.teamId); + } + // associate sees own leads + const myAssociate = users.find((u) => u.role === "associate"); + if (!myAssociate) return []; + return leads.filter((l) => l.ownerId === myAssociate.id); + }, [role, leads, users]); + + function createLead(payload) { + const newLead = { id: `L-${Math.floor(Math.random() * 9000) + 1000}`, createdAt: new Date().toISOString().slice(0, 10), ...payload }; + setLeads((s) => [newLead, ...s]); + return newLead; + } + + function updateLead(id, patch) { + setLeads((s) => s.map((l) => (l.id === id ? { ...l, ...patch } : l))); + } + + function bulkAssign(leadIds, userId) { + setLeads((s) => s.map((l) => (leadIds.includes(l.id) ? { ...l, ownerId: userId, teamId: users.find((u) => u.id === userId)?.teamId } : l))); + } + + // UI + return ( +
+
+
+
+ + +
+ {selectedPage === "dashboard" && ( + { + setSelectedLead(lead); + setSelectedPage("lead"); + }} + /> + )} + + {selectedPage === "leads" && ( + { + setSelectedLead(l); + setSelectedPage("lead"); + }} + onCreate={(payload) => createLead(payload)} + onUpdate={updateLead} + onBulkAssign={bulkAssign} + role={role} + /> + )} + + {selectedPage === "teams" && } + + {selectedPage === "users" && } + + {selectedPage === "lead" && selectedLead && ( + setSelectedPage("leads")} onSave={updateLead} users={users} /> + )} +
+
+
+
+ ); +} + +function Header({ role, setRole, selectedPage, setSelectedPage }) { + return ( +
+
+
M
+
+

moom — Har Ghar Ki Dukaan

+

Sales CRM Portal Prototype

+
+
+ +
+ + +
+ + +
+
+
+ ); +} + +function Sidebar({ selectedPage, setSelectedPage, role }) { + return ( + + ); +} + +function Dashboard({ role, leads, visibleLeads, users, teams, onOpenLead }) { + const totalLeads = leads.length; + const myLeads = visibleLeads.length; + const byStage = leads.reduce((acc, l) => { + acc[l.stage] = (acc[l.stage] || 0) + 1; + return acc; + }, {}); + + return ( +
+
+ + + +
+ +
+

Pipeline Overview

+
+ {Object.keys(byStage).length === 0 &&
No leads yet
} + {Object.entries(byStage).map(([stage, count]) => ( +
+
{stage}
+
{count}
+
+ ))} +
+
+ +
+

Recent Leads

+
+ {visibleLeads.slice(0, 5).map((l) => ( +
+
+
{l.title}
+
{l.company} • {l.contact} • {l.territory}
+
+
+
{l.stage}
+ +
+
+ ))} +
+
+
+ ); +} + +function StatCard({ title, value }) { + return ( +
+
{title}
+
{value}
+
+ ); +} + +function LeadsList({ leads, users, teams, onOpenLead, onCreate, onUpdate, onBulkAssign, role }) { + const [filter, setFilter] = useState({ q: "", stage: "", territory: "" }); + const [selectedIds, setSelectedIds] = useState([]); + const filtered = leads.filter((l) => (filter.q ? `${l.title} ${l.company} ${l.contact}`.toLowerCase().includes(filter.q.toLowerCase()) : true) && (filter.stage ? l.stage === filter.stage : true) && (filter.territory ? l.territory === filter.territory : true)); + + function toggleSelect(id) { + setSelectedIds((s) => (s.includes(id) ? s.filter((x) => x !== id) : [...s, id])); + } + + return ( +
+
+

Leads

+
+ + +
+
+ +
+
+ setFilter({ ...filter, q: e.target.value })} /> + + +
+ + + + + + + + + + + + + + + + {filtered.map((l) => ( + + + + + + + + + + + ))} + +
0} onChange={(e) => setSelectedIds(e.target.checked ? filtered.map((f) => f.id) : [])} />LeadCompanyContactOwnerStageValue
toggleSelect(l.id)} />
{l.title}
{l.createdAt}
{l.company}{l.contact} • {l.phone}{users.find((u) => u.id === l.ownerId)?.name ?? "-"}{l.stage}₹{l.expectedValue}
+ +
+
{filtered.length} results
+
+ + +
+
+
+
+ ); +} + +function CreateLeadButton({ onCreate, users, teams, currentRole }) { + const [open, setOpen] = useState(false); + const [form, setForm] = useState({ title: "", company: "", contact: "", phone: "", ownerId: "", teamId: "", stage: "New", expectedValue: 0, source: "Website", territory: "" }); + + function submit() { + if (!form.contact || !form.company) return alert("Company and Contact are required"); + onCreate({ ...form, ownerId: form.ownerId || (currentRole === "associate" ? users.find((u) => u.role === "associate")?.id : users.find((u) => u.role === "team_lead")?.id) }); + setOpen(false); + setForm({ title: "", company: "", contact: "", phone: "", ownerId: "", teamId: "", stage: "New", expectedValue: 0, source: "Website", territory: "" }); + } + + return ( +
+ + {open && ( +
+
+

Create Lead

+
+ setForm({ ...form, company: e.target.value })} className="border rounded px-2 py-1" /> + setForm({ ...form, contact: e.target.value })} className="border rounded px-2 py-1" /> + setForm({ ...form, phone: e.target.value })} className="border rounded px-2 py-1" /> + setForm({ ...form, title: e.target.value })} className="border rounded px-2 py-1" /> + + setForm({ ...form, expectedValue: Number(e.target.value) })} className="border rounded px-2 py-1" /> + + setForm({ ...form, territory: e.target.value })} className="border rounded px-2 py-1 col-span-2" /> +
+ +
+ + +
+
+
+ )} +
+ ); +} + +function LeadDetail({ lead, onClose, onSave, users }) { + const [draft, setDraft] = useState(lead); + + function save() { + onSave(lead.id, draft); + onClose(); + } + + return ( +
+
+
+

{lead.title}

+
{lead.company} • {lead.contact} • {lead.territory}
+
+
+ + +
+
+ +
+
+ +