) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ const handleRangeSelect = (range: (typeof dateRanges)[0]) => {
+ setSelectedRange(range);
+ handleClose();
+ };
+
+ return (
+
+ }
+ startIcon={}
+ size="small"
+ >
+ {selectedRange.label}
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmHeader.tsx b/src/crm/components/CrmHeader.tsx
new file mode 100644
index 0000000..526aad9
--- /dev/null
+++ b/src/crm/components/CrmHeader.tsx
@@ -0,0 +1,48 @@
+import * as React from "react";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import NotificationsRoundedIcon from "@mui/icons-material/NotificationsRounded";
+import MenuButton from "../../dashboard/components/MenuButton";
+import ColorModeIconDropdown from "../../shared-theme/ColorModeIconDropdown";
+import CrmSearch from "./CrmSearch";
+import CrmNavbarBreadcrumbs from "./CrmNavbarBreadcrumbs";
+import Button from "@mui/material/Button";
+import CalendarTodayRoundedIcon from "@mui/icons-material/CalendarTodayRounded";
+
+export default function CrmHeader() {
+ return (
+
+
+
+
+ CRM Dashboard
+
+
+
+
+ }
+ >
+ This Month
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmLeadsBySourceChart.tsx b/src/crm/components/CrmLeadsBySourceChart.tsx
new file mode 100644
index 0000000..ca751e8
--- /dev/null
+++ b/src/crm/components/CrmLeadsBySourceChart.tsx
@@ -0,0 +1,76 @@
+import * as React from "react";
+import { useTheme } from "@mui/material/styles";
+import Box from "@mui/material/Box";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Typography from "@mui/material/Typography";
+import { PieChart } from "@mui/x-charts/PieChart";
+
+// Sample lead source data
+const leadSources = [
+ { id: 0, value: 35, label: "Website", color: "#3f51b5" },
+ { id: 1, value: 25, label: "Referrals", color: "#2196f3" },
+ { id: 2, value: 20, label: "Social Media", color: "#4caf50" },
+ { id: 3, value: 15, label: "Email Campaigns", color: "#ff9800" },
+ { id: 4, value: 5, label: "Other", color: "#9e9e9e" },
+];
+
+export default function CrmLeadsBySourceChart() {
+ const theme = useTheme();
+
+ return (
+
+
+
+ Leads by Source
+
+
+
+ `${item.value}%`,
+ arcLabelMinAngle: 20,
+ innerRadius: 60,
+ paddingAngle: 2,
+ cornerRadius: 4,
+ valueFormatter: (value) => `${value}%`,
+ },
+ ]}
+ height={280}
+ slotProps={{
+ legend: {
+ position: { vertical: "middle", horizontal: "right" },
+ direction: "column",
+ itemMarkWidth: 10,
+ itemMarkHeight: 10,
+ markGap: 5,
+ itemGap: 8,
+ },
+ }}
+ margin={{ right: 120 }}
+ />
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmMainContent.tsx b/src/crm/components/CrmMainContent.tsx
new file mode 100644
index 0000000..f85ed8c
--- /dev/null
+++ b/src/crm/components/CrmMainContent.tsx
@@ -0,0 +1,141 @@
+import * as React from "react";
+import Grid from "@mui/material/Grid";
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import AddRoundedIcon from "@mui/icons-material/AddRounded";
+import Copyright from "../../dashboard/internals/components/Copyright";
+import CrmStatCard from "./CrmStatCard";
+import CrmRecentDealsTable from "./CrmRecentDealsTable";
+import CrmUpcomingTasks from "./CrmUpcomingTasks";
+import CrmSalesChart from "./CrmSalesChart";
+import CrmLeadsBySourceChart from "./CrmLeadsBySourceChart";
+import CrmActivitiesTimeline from "./CrmActivitiesTimeline";
+import CrmCustomerDistributionMap from "./CrmCustomerDistributionMap";
+
+// Sample data for stat cards
+const statCardsData = [
+ {
+ title: "Total Customers",
+ value: "2,543",
+ interval: "Last 30 days",
+ trend: "up",
+ trendValue: "+15%",
+ data: [
+ 200, 240, 260, 280, 300, 320, 340, 360, 380, 400, 420, 440, 460, 480, 500,
+ 520, 540, 560, 580, 600, 620, 640, 660, 680, 700, 720, 740, 760, 780, 800,
+ ],
+ },
+ {
+ title: "Deals Won",
+ value: "$542K",
+ interval: "Last 30 days",
+ trend: "up",
+ trendValue: "+23%",
+ data: [
+ 400, 420, 440, 460, 480, 500, 520, 540, 560, 580, 600, 620, 640, 660, 680,
+ 700, 720, 740, 760, 780, 800, 820, 840, 860, 880, 900, 920, 940, 960, 980,
+ ],
+ },
+ {
+ title: "New Leads",
+ value: "456",
+ interval: "Last 30 days",
+ trend: "up",
+ trendValue: "+12%",
+ data: [
+ 300, 310, 320, 330, 340, 350, 360, 370, 380, 390, 400, 410, 420, 430, 440,
+ 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590,
+ ],
+ },
+ {
+ title: "Conversion Rate",
+ value: "28%",
+ interval: "Last 30 days",
+ trend: "down",
+ trendValue: "-5%",
+ data: [
+ 35, 33, 32, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 22, 23, 24, 25, 26,
+ 27, 28, 29, 30, 29, 28, 27, 26, 25, 24, 23, 22,
+ ],
+ },
+];
+
+export default function CrmMainContent() {
+ return (
+
+ {/* Header with action buttons */}
+
+
+ Dashboard Overview
+
+
+ }
+ sx={{ mr: 1 }}
+ >
+ New Lead
+
+ }>
+ New Deal
+
+
+
+
+ {/* Stats Cards row */}
+
+ {statCardsData.map((card, index) => (
+
+
+
+ ))}
+
+
+ {/* Charts row */}
+
+
+
+
+
+
+
+
+
+ {/* Tables & Other content */}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Map row */}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmMainDashboard.tsx b/src/crm/components/CrmMainDashboard.tsx
new file mode 100644
index 0000000..171117e
--- /dev/null
+++ b/src/crm/components/CrmMainDashboard.tsx
@@ -0,0 +1,131 @@
+import * as React from "react";
+import Grid from "@mui/material/Grid";
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import AddRoundedIcon from "@mui/icons-material/AddRounded";
+import Copyright from "../../dashboard/internals/components/Copyright";
+import CrmStatCard from "./CrmStatCard";
+import CrmRecentDealsTable from "./CrmRecentDealsTable";
+import CrmUpcomingTasks from "./CrmUpcomingTasks";
+import CrmSalesChart from "./CrmSalesChart";
+import CrmLeadsBySourceChart from "./CrmLeadsBySourceChart";
+
+// Sample data for stat cards
+const statCardsData = [
+ {
+ title: "Total Customers",
+ value: "2,543",
+ interval: "Last 30 days",
+ trend: "up",
+ trendValue: "+15%",
+ data: [
+ 200, 240, 260, 280, 300, 320, 340, 360, 380, 400, 420, 440, 460, 480, 500,
+ 520, 540, 560, 580, 600, 620, 640, 660, 680, 700, 720, 740, 760, 780, 800,
+ ],
+ },
+ {
+ title: "Deals Won",
+ value: "$542K",
+ interval: "Last 30 days",
+ trend: "up",
+ trendValue: "+23%",
+ data: [
+ 400, 420, 440, 460, 480, 500, 520, 540, 560, 580, 600, 620, 640, 660, 680,
+ 700, 720, 740, 760, 780, 800, 820, 840, 860, 880, 900, 920, 940, 960, 980,
+ ],
+ },
+ {
+ title: "New Leads",
+ value: "456",
+ interval: "Last 30 days",
+ trend: "up",
+ trendValue: "+12%",
+ data: [
+ 300, 310, 320, 330, 340, 350, 360, 370, 380, 390, 400, 410, 420, 430, 440,
+ 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590,
+ ],
+ },
+ {
+ title: "Conversion Rate",
+ value: "28%",
+ interval: "Last 30 days",
+ trend: "down",
+ trendValue: "-5%",
+ data: [
+ 35, 33, 32, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 22, 23, 24, 25, 26,
+ 27, 28, 29, 30, 29, 28, 27, 26, 25, 24, 23, 22,
+ ],
+ },
+];
+
+export default function CrmMainDashboard() {
+ return (
+
+ {/* Header with action buttons */}
+
+
+ Dashboard Overview
+
+
+ }
+ sx={{ mr: 1 }}
+ >
+ New Lead
+
+ }>
+ New Deal
+
+
+
+
+ {/* Stats Cards row */}
+
+ {statCardsData.map((card, index) => (
+
+
+
+ ))}
+
+
+ {/* Charts row */}
+
+
+
+
+
+
+
+
+
+ {/* Tables & Other content */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmMenuContent.tsx b/src/crm/components/CrmMenuContent.tsx
new file mode 100644
index 0000000..658ea80
--- /dev/null
+++ b/src/crm/components/CrmMenuContent.tsx
@@ -0,0 +1,75 @@
+import * as React from "react";
+import { useNavigate, useLocation } from "react-router-dom";
+import Box from "@mui/material/Box"; // Added the missing import
+import List from "@mui/material/List";
+import ListItem from "@mui/material/ListItem";
+import ListItemButton from "@mui/material/ListItemButton";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import ListItemText from "@mui/material/ListItemText";
+import Stack from "@mui/material/Stack";
+import Divider from "@mui/material/Divider";
+import DashboardRoundedIcon from "@mui/icons-material/DashboardRounded";
+import PeopleRoundedIcon from "@mui/icons-material/PeopleRounded";
+import BusinessCenterRoundedIcon from "@mui/icons-material/BusinessCenterRounded";
+import ContactsRoundedIcon from "@mui/icons-material/ContactsRounded";
+import AssignmentRoundedIcon from "@mui/icons-material/AssignmentRounded";
+import AssessmentRoundedIcon from "@mui/icons-material/AssessmentRounded";
+import SettingsRoundedIcon from "@mui/icons-material/SettingsRounded";
+import HelpOutlineRoundedIcon from "@mui/icons-material/HelpOutlineRounded";
+
+const mainListItems = [
+ { text: "Dashboard", icon: , path: "/" },
+ { text: "Customers", icon: , path: "/customers" },
+ { text: "Deals", icon: , path: "/deals" },
+ { text: "Contacts", icon: , path: "/contacts" },
+ { text: "Tasks", icon: , path: "/tasks" },
+ { text: "Reports", icon: , path: "/reports" },
+];
+
+const secondaryListItems = [
+ { text: "Settings", icon: , path: "/settings" },
+ { text: "Help & Support", icon: , path: "/help" },
+];
+
+export default function CrmMenuContent() {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const handleNavigation = (path: string) => {
+ navigate(path);
+ };
+
+ return (
+
+
+ {mainListItems.map((item, index) => (
+
+ handleNavigation(item.path)}
+ >
+ {item.icon}
+
+
+
+ ))}
+
+
+
+
+ {secondaryListItems.map((item, index) => (
+
+ handleNavigation(item.path)}
+ >
+ {item.icon}
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmNavbarBreadcrumbs.tsx b/src/crm/components/CrmNavbarBreadcrumbs.tsx
new file mode 100644
index 0000000..4e24d51
--- /dev/null
+++ b/src/crm/components/CrmNavbarBreadcrumbs.tsx
@@ -0,0 +1,55 @@
+import * as React from "react";
+import { useLocation, Link as RouterLink } from "react-router-dom";
+import Breadcrumbs from "@mui/material/Breadcrumbs";
+import Link from "@mui/material/Link";
+import Typography from "@mui/material/Typography";
+import HomeRoundedIcon from "@mui/icons-material/HomeRounded";
+import NavigateNextRoundedIcon from "@mui/icons-material/NavigateNextRounded";
+
+function capitalizeFirstLetter(string: string) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+export default function CrmNavbarBreadcrumbs() {
+ const location = useLocation();
+ const pathnames = location.pathname.split("/").filter((x) => x);
+
+ return (
+ }
+ aria-label="breadcrumb"
+ sx={{ mb: 1 }}
+ >
+
+
+ Home
+
+ {pathnames.map((value, index) => {
+ const last = index === pathnames.length - 1;
+ const to = `/${pathnames.slice(0, index + 1).join("/")}`;
+
+ return last ? (
+
+ {capitalizeFirstLetter(value)}
+
+ ) : (
+
+ {capitalizeFirstLetter(value)}
+
+ );
+ })}
+
+ );
+}
diff --git a/src/crm/components/CrmOptionsMenu.tsx b/src/crm/components/CrmOptionsMenu.tsx
new file mode 100644
index 0000000..941253f
--- /dev/null
+++ b/src/crm/components/CrmOptionsMenu.tsx
@@ -0,0 +1,68 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import IconButton from "@mui/material/IconButton";
+import Menu from "@mui/material/Menu";
+import MenuItem from "@mui/material/MenuItem";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import ListItemText from "@mui/material/ListItemText";
+import Divider from "@mui/material/Divider";
+import MoreVertRoundedIcon from "@mui/icons-material/MoreVertRounded";
+import PersonRoundedIcon from "@mui/icons-material/PersonRounded";
+import ExitToAppRoundedIcon from "@mui/icons-material/ExitToAppRounded";
+import SettingsRoundedIcon from "@mui/icons-material/SettingsRounded";
+
+export default function CrmOptionsMenu() {
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmRecentDealsTable.tsx b/src/crm/components/CrmRecentDealsTable.tsx
new file mode 100644
index 0000000..a191866
--- /dev/null
+++ b/src/crm/components/CrmRecentDealsTable.tsx
@@ -0,0 +1,186 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import Typography from "@mui/material/Typography";
+import Chip from "@mui/material/Chip";
+import Stack from "@mui/material/Stack";
+import Avatar from "@mui/material/Avatar";
+import IconButton from "@mui/material/IconButton";
+import MoreVertRoundedIcon from "@mui/icons-material/MoreVertRounded";
+import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
+import Button from "@mui/material/Button";
+
+// Sample data for recent deals
+const recentDeals = [
+ {
+ id: 1,
+ name: "Enterprise Software Package",
+ customer: { name: "Acme Corp", avatar: "A" },
+ value: 125000,
+ stage: "Proposal",
+ probability: 75,
+ closingDate: "2023-09-30",
+ },
+ {
+ id: 2,
+ name: "Cloud Migration Service",
+ customer: { name: "TechSolutions Inc", avatar: "T" },
+ value: 87500,
+ stage: "Negotiation",
+ probability: 90,
+ closingDate: "2023-10-15",
+ },
+ {
+ id: 3,
+ name: "Website Redesign Project",
+ customer: { name: "Global Media", avatar: "G" },
+ value: 45000,
+ stage: "Discovery",
+ probability: 60,
+ closingDate: "2023-11-05",
+ },
+ {
+ id: 4,
+ name: "CRM Implementation",
+ customer: { name: "RetailGiant", avatar: "R" },
+ value: 95000,
+ stage: "Closed Won",
+ probability: 100,
+ closingDate: "2023-09-15",
+ },
+ {
+ id: 5,
+ name: "IT Infrastructure Upgrade",
+ customer: { name: "HealthCare Pro", avatar: "H" },
+ value: 135000,
+ stage: "Negotiation",
+ probability: 85,
+ closingDate: "2023-10-22",
+ },
+];
+
+// Function to get color based on deal stage
+const getStageColor = (
+ stage: string,
+): "default" | "primary" | "success" | "warning" | "info" => {
+ switch (stage) {
+ case "Discovery":
+ return "info";
+ case "Proposal":
+ return "primary";
+ case "Negotiation":
+ return "warning";
+ case "Closed Won":
+ return "success";
+ default:
+ return "default";
+ }
+};
+
+// Format currency
+const formatCurrency = (value: number) => {
+ return new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ maximumFractionDigits: 0,
+ }).format(value);
+};
+
+// Format date
+const formatDate = (dateString: string) => {
+ const options: Intl.DateTimeFormatOptions = {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ };
+ return new Date(dateString).toLocaleDateString("en-US", options);
+};
+
+export default function CrmRecentDealsTable() {
+ return (
+
+
+
+
+ Recent Deals
+
+ } size="small">
+ View All
+
+
+
+
+
+
+
+ Deal Name
+ Customer
+ Value
+ Stage
+ Probability
+ Closing Date
+ Actions
+
+
+
+ {recentDeals.map((deal) => (
+
+ {deal.name}
+
+
+
+ {deal.customer.avatar}
+
+
+ {deal.customer.name}
+
+
+
+
+ {formatCurrency(deal.value)}
+
+
+
+
+ {deal.probability}%
+ {formatDate(deal.closingDate)}
+
+
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmSalesChart.tsx b/src/crm/components/CrmSalesChart.tsx
new file mode 100644
index 0000000..38ebdcb
--- /dev/null
+++ b/src/crm/components/CrmSalesChart.tsx
@@ -0,0 +1,173 @@
+import * as React from "react";
+import { useTheme } from "@mui/material/styles";
+import Box from "@mui/material/Box";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Typography from "@mui/material/Typography";
+import Stack from "@mui/material/Stack";
+import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
+import ToggleButton from "@mui/material/ToggleButton";
+import { BarChart } from "@mui/x-charts/BarChart";
+
+export default function CrmSalesChart() {
+ const theme = useTheme();
+ const [timeRange, setTimeRange] = React.useState("year");
+
+ const handleTimeRangeChange = (
+ event: React.MouseEvent,
+ newTimeRange: string | null,
+ ) => {
+ if (newTimeRange !== null) {
+ setTimeRange(newTimeRange);
+ }
+ };
+
+ // Generate monthly data
+ const currentYear = new Date().getFullYear();
+ const monthNames = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+
+ // Sample data (in a real app this would come from an API)
+ const salesData = [
+ 180000, 210000, 250000, 220000, 270000, 310000, 330000, 350000, 390000,
+ 410000, 430000, 470000,
+ ];
+ const targetsData = [
+ 200000, 220000, 240000, 260000, 280000, 300000, 320000, 340000, 360000,
+ 380000, 400000, 450000,
+ ];
+ const projectedData = [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 450000,
+ 500000,
+ ];
+
+ const xAxisData = {
+ scaleType: "band" as const,
+ data: monthNames,
+ tickLabelStyle: {
+ angle: 0,
+ textAnchor: "middle",
+ fontSize: 12,
+ },
+ };
+
+ // Format y-axis labels to show $ and K for thousands
+ const formatYAxis = (value: number) => {
+ if (value >= 1000000) {
+ return `$${(value / 1000000).toFixed(1)}M`;
+ }
+ if (value >= 1000) {
+ return `$${(value / 1000).toFixed(0)}K`;
+ }
+ return `$${value}`;
+ };
+
+ return (
+
+
+
+
+ Sales Performance
+
+
+
+ Month
+
+
+ Quarter
+
+
+ Year
+
+
+
+
+
+ (value ? formatYAxis(value) : ""),
+ },
+ {
+ data: targetsData,
+ label: "Targets",
+ color: theme.palette.grey[400],
+ valueFormatter: (value) => (value ? formatYAxis(value) : ""),
+ },
+ {
+ data: projectedData,
+ label: "Projected",
+ color: theme.palette.secondary.main,
+ valueFormatter: (value) => (value ? formatYAxis(value) : ""),
+ },
+ ]}
+ xAxis={[xAxisData]}
+ yAxis={[
+ {
+ label: "Revenue",
+ valueFormatter: formatYAxis,
+ },
+ ]}
+ height={300}
+ margin={{ top: 10, bottom: 30, left: 60, right: 10 }}
+ slotProps={{
+ legend: {
+ position: { vertical: "top", horizontal: "middle" },
+ itemMarkWidth: 10,
+ itemMarkHeight: 10,
+ markGap: 5,
+ itemGap: 10,
+ },
+ }}
+ />
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmSearch.tsx b/src/crm/components/CrmSearch.tsx
new file mode 100644
index 0000000..dca0de4
--- /dev/null
+++ b/src/crm/components/CrmSearch.tsx
@@ -0,0 +1,65 @@
+import * as React from "react";
+import InputBase from "@mui/material/InputBase";
+import SearchRoundedIcon from "@mui/icons-material/SearchRounded";
+import { alpha, styled } from "@mui/material/styles";
+
+const SearchWrapper = styled("div")(({ theme }) => ({
+ position: "relative",
+ borderRadius: theme.shape.borderRadius,
+ backgroundColor: alpha(theme.palette.common.black, 0.04),
+ "&:hover": {
+ backgroundColor: alpha(theme.palette.common.black, 0.06),
+ },
+ marginLeft: 0,
+ width: "100%",
+ [theme.breakpoints.up("sm")]: {
+ width: "auto",
+ marginLeft: theme.spacing(1),
+ },
+ ...theme.applyStyles("dark", {
+ backgroundColor: alpha(theme.palette.common.white, 0.06),
+ "&:hover": {
+ backgroundColor: alpha(theme.palette.common.white, 0.1),
+ },
+ }),
+}));
+
+const SearchIconWrapper = styled("div")(({ theme }) => ({
+ padding: theme.spacing(0, 2),
+ height: "100%",
+ position: "absolute",
+ pointerEvents: "none",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+}));
+
+const StyledInputBase = styled(InputBase)(({ theme }) => ({
+ color: "inherit",
+ "& .MuiInputBase-input": {
+ padding: theme.spacing(1, 1, 1, 0),
+ paddingLeft: `calc(1em + ${theme.spacing(4)})`,
+ transition: theme.transitions.create("width"),
+ width: "100%",
+ [theme.breakpoints.up("sm")]: {
+ width: "12ch",
+ "&:focus": {
+ width: "20ch",
+ },
+ },
+ },
+}));
+
+export default function CrmSearch() {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmSelectCompany.tsx b/src/crm/components/CrmSelectCompany.tsx
new file mode 100644
index 0000000..b9aaaed
--- /dev/null
+++ b/src/crm/components/CrmSelectCompany.tsx
@@ -0,0 +1,56 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import MenuItem from "@mui/material/MenuItem";
+import FormControl from "@mui/material/FormControl";
+import Select, { SelectChangeEvent } from "@mui/material/Select";
+import BusinessRoundedIcon from "@mui/icons-material/BusinessRounded";
+
+export default function CrmSelectCompany() {
+ const [company, setCompany] = React.useState("acme");
+
+ const handleChange = (event: SelectChangeEvent) => {
+ setCompany(event.target.value as string);
+ };
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmSideMenu.tsx b/src/crm/components/CrmSideMenu.tsx
new file mode 100644
index 0000000..e9dfd08
--- /dev/null
+++ b/src/crm/components/CrmSideMenu.tsx
@@ -0,0 +1,91 @@
+import * as React from "react";
+import { styled } from "@mui/material/styles";
+import { useNavigate, useLocation } from "react-router-dom";
+import Avatar from "@mui/material/Avatar";
+import MuiDrawer, { drawerClasses } from "@mui/material/Drawer";
+import Box from "@mui/material/Box";
+import Divider from "@mui/material/Divider";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import CrmSelectCompany from "./CrmSelectCompany";
+import CrmMenuContent from "./CrmMenuContent";
+import CrmOptionsMenu from "./CrmOptionsMenu";
+
+const drawerWidth = 240;
+
+const Drawer = styled(MuiDrawer)({
+ width: drawerWidth,
+ flexShrink: 0,
+ boxSizing: "border-box",
+ mt: 10,
+ [`& .${drawerClasses.paper}`]: {
+ width: drawerWidth,
+ boxSizing: "border-box",
+ },
+});
+
+export default function CrmSideMenu() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ AT
+
+
+
+ Alex Thompson
+
+
+ alex@acmecrm.com
+
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmSideMenuMobile.tsx b/src/crm/components/CrmSideMenuMobile.tsx
new file mode 100644
index 0000000..8f5eaca
--- /dev/null
+++ b/src/crm/components/CrmSideMenuMobile.tsx
@@ -0,0 +1,124 @@
+import * as React from "react";
+import { useNavigate, useLocation } from "react-router-dom";
+import Box from "@mui/material/Box";
+import Drawer from "@mui/material/Drawer";
+import Divider from "@mui/material/Divider";
+import List from "@mui/material/List";
+import ListItem from "@mui/material/ListItem";
+import ListItemButton from "@mui/material/ListItemButton";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import ListItemText from "@mui/material/ListItemText";
+import Typography from "@mui/material/Typography";
+import DashboardRoundedIcon from "@mui/icons-material/DashboardRounded";
+import PeopleRoundedIcon from "@mui/icons-material/PeopleRounded";
+import BusinessCenterRoundedIcon from "@mui/icons-material/BusinessCenterRounded";
+import ContactsRoundedIcon from "@mui/icons-material/ContactsRounded";
+import AssignmentRoundedIcon from "@mui/icons-material/AssignmentRounded";
+import AssessmentRoundedIcon from "@mui/icons-material/AssessmentRounded";
+import SettingsRoundedIcon from "@mui/icons-material/SettingsRounded";
+import HelpOutlineRoundedIcon from "@mui/icons-material/HelpOutlineRounded";
+import { CrmLogo } from "./CrmAppNavbar";
+
+const mainListItems = [
+ { text: "Dashboard", icon: , path: "/" },
+ { text: "Customers", icon: , path: "/customers" },
+ { text: "Deals", icon: , path: "/deals" },
+ { text: "Contacts", icon: , path: "/contacts" },
+ { text: "Tasks", icon: , path: "/tasks" },
+ { text: "Reports", icon: , path: "/reports" },
+];
+
+const secondaryListItems = [
+ { text: "Settings", icon: , path: "/settings" },
+ { text: "Help & Support", icon: , path: "/help" },
+];
+
+interface CrmSideMenuMobileProps {
+ open: boolean;
+ toggleDrawer: (open: boolean) => () => void;
+}
+
+export default function CrmSideMenuMobile({
+ open,
+ toggleDrawer,
+}: CrmSideMenuMobileProps) {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const handleNavigation = (path: string) => {
+ navigate(path);
+ toggleDrawer(false)();
+ };
+
+ return (
+
+
+
+
+
+ Acme CRM
+
+
+
+
+ {mainListItems.map((item, index) => (
+
+ handleNavigation(item.path)}
+ >
+ {item.icon}
+
+
+
+ ))}
+
+
+
+
+
+ {secondaryListItems.map((item, index) => (
+
+ handleNavigation(item.path)}
+ >
+ {item.icon}
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmStatCard.tsx b/src/crm/components/CrmStatCard.tsx
new file mode 100644
index 0000000..98ee8f0
--- /dev/null
+++ b/src/crm/components/CrmStatCard.tsx
@@ -0,0 +1,139 @@
+import * as React from "react";
+import { useTheme } from "@mui/material/styles";
+import Box from "@mui/material/Box";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Chip from "@mui/material/Chip";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
+import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
+import { SparkLineChart } from "@mui/x-charts/SparkLineChart";
+import { areaElementClasses } from "@mui/x-charts/LineChart";
+
+export type CrmStatCardProps = {
+ title: string;
+ value: string;
+ interval: string;
+ trend: "up" | "down";
+ trendValue: string;
+ data: number[];
+};
+
+function AreaGradient({ color, id }: { color: string; id: string }) {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default function CrmStatCard({
+ title,
+ value,
+ interval,
+ trend,
+ trendValue,
+ data,
+}: CrmStatCardProps) {
+ const theme = useTheme();
+
+ const trendColors = {
+ up:
+ theme.palette.mode === "light"
+ ? theme.palette.success.main
+ : theme.palette.success.dark,
+ down:
+ theme.palette.mode === "light"
+ ? theme.palette.error.main
+ : theme.palette.error.dark,
+ };
+
+ const labelColors = {
+ up: "success" as const,
+ down: "error" as const,
+ };
+
+ const trendIcons = {
+ up: ,
+ down: ,
+ };
+
+ const color = labelColors[trend];
+ const chartColor = trendColors[trend];
+ const trendIcon = trendIcons[trend];
+
+ return (
+
+
+
+ {title}
+
+
+
+
+
+ {value}
+
+
+
+
+ {interval}
+
+
+
+ `Day ${i + 1}`,
+ ),
+ }}
+ sx={{
+ [`& .${areaElementClasses.root}`]: {
+ fill: `url(#area-gradient-${title.replace(/\s+/g, "-").toLowerCase()})`,
+ },
+ }}
+ >
+
+
+
+
+
+
+ );
+}
diff --git a/src/crm/components/CrmUpcomingTasks.tsx b/src/crm/components/CrmUpcomingTasks.tsx
new file mode 100644
index 0000000..18e605a
--- /dev/null
+++ b/src/crm/components/CrmUpcomingTasks.tsx
@@ -0,0 +1,184 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Typography from "@mui/material/Typography";
+import List from "@mui/material/List";
+import ListItem from "@mui/material/ListItem";
+import ListItemButton from "@mui/material/ListItemButton";
+import ListItemText from "@mui/material/ListItemText";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import Checkbox from "@mui/material/Checkbox";
+import IconButton from "@mui/material/IconButton";
+import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import Chip from "@mui/material/Chip";
+
+// Sample data for upcoming tasks
+const upcomingTasks = [
+ {
+ id: 1,
+ task: "Follow up with TechSolutions Inc on cloud proposal",
+ completed: false,
+ priority: "high",
+ dueDate: "Today, 2:00 PM",
+ },
+ {
+ id: 2,
+ task: "Prepare presentation for Global Media website project",
+ completed: false,
+ priority: "medium",
+ dueDate: "Tomorrow, 10:00 AM",
+ },
+ {
+ id: 3,
+ task: "Call HealthCare Pro about contract details",
+ completed: false,
+ priority: "high",
+ dueDate: "Today, 4:30 PM",
+ },
+ {
+ id: 4,
+ task: "Update CRM implementation timeline for RetailGiant",
+ completed: true,
+ priority: "medium",
+ dueDate: "Yesterday",
+ },
+ {
+ id: 5,
+ task: "Send proposal documents to Acme Corp",
+ completed: false,
+ priority: "low",
+ dueDate: "Sep 28, 2023",
+ },
+];
+
+// Function to get priority color
+const getPriorityColor = (
+ priority: string,
+): "error" | "warning" | "default" => {
+ switch (priority) {
+ case "high":
+ return "error";
+ case "medium":
+ return "warning";
+ default:
+ return "default";
+ }
+};
+
+export default function CrmUpcomingTasks() {
+ const [tasks, setTasks] = React.useState(upcomingTasks);
+
+ const handleToggle = (id: number) => () => {
+ setTasks(
+ tasks.map((task) =>
+ task.id === id ? { ...task, completed: !task.completed } : task,
+ ),
+ );
+ };
+
+ return (
+
+
+
+
+ Upcoming Tasks
+
+ } size="small">
+ View All
+
+
+
+
+ {tasks.map((task) => {
+ const labelId = `checkbox-list-label-${task.id}`;
+
+ return (
+
+
+
+ }
+ disablePadding
+ >
+
+
+
+
+
+ {task.task}
+
+ }
+ secondary={
+
+
+
+ {task.dueDate}
+
+
+ }
+ />
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/src/crm/components/CustomerModal.tsx b/src/crm/components/CustomerModal.tsx
new file mode 100644
index 0000000..ec72f50
--- /dev/null
+++ b/src/crm/components/CustomerModal.tsx
@@ -0,0 +1,399 @@
+import * as React from "react";
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ TextField,
+ Grid,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem,
+ Box,
+ CircularProgress,
+ Alert,
+} from "@mui/material";
+import {
+ User,
+ CreateUserRequest,
+ UpdateUserRequest,
+} from "../services/usersApi";
+
+interface CustomerModalProps {
+ open: boolean;
+ onClose: () => void;
+ onSave: (userData: CreateUserRequest | UpdateUserRequest) => Promise;
+ user?: User | null;
+ loading?: boolean;
+}
+
+const initialFormData = {
+ email: "",
+ username: "",
+ password: "",
+ firstName: "",
+ lastName: "",
+ title: "Mr",
+ gender: "male",
+ phone: "",
+ cell: "",
+ streetNumber: "",
+ streetName: "",
+ city: "",
+ state: "",
+ country: "",
+ postcode: "",
+};
+
+export default function CustomerModal({
+ open,
+ onClose,
+ onSave,
+ user,
+ loading = false,
+}: CustomerModalProps) {
+ const [formData, setFormData] = React.useState(initialFormData);
+ const [error, setError] = React.useState(null);
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
+
+ const isEditMode = Boolean(user);
+
+ React.useEffect(() => {
+ if (user) {
+ setFormData({
+ email: user.email || "",
+ username: user.login.username || "",
+ password: "",
+ firstName: user.name.first || "",
+ lastName: user.name.last || "",
+ title: user.name.title || "Mr",
+ gender: user.gender || "male",
+ phone: user.phone || "",
+ cell: user.cell || "",
+ streetNumber: user.location?.street?.number?.toString() || "",
+ streetName: user.location?.street?.name || "",
+ city: user.location?.city || "",
+ state: user.location?.state || "",
+ country: user.location?.country || "",
+ postcode: user.location?.postcode || "",
+ });
+ } else {
+ setFormData(initialFormData);
+ }
+ setError(null);
+ }, [user, open]);
+
+ const handleInputChange =
+ (field: string) =>
+ (
+ event:
+ | React.ChangeEvent
+ | { target: { value: unknown } },
+ ) => {
+ setFormData((prev) => ({
+ ...prev,
+ [field]: event.target.value as string,
+ }));
+ setError(null);
+ };
+
+ const handleSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+ setError(null);
+ setIsSubmitting(true);
+
+ try {
+ if (isEditMode) {
+ const updateData: UpdateUserRequest = {
+ email: formData.email,
+ name: {
+ first: formData.firstName,
+ last: formData.lastName,
+ title: formData.title,
+ },
+ gender: formData.gender,
+ phone: formData.phone,
+ cell: formData.cell,
+ location: {
+ street: {
+ number: parseInt(formData.streetNumber) || 0,
+ name: formData.streetName,
+ },
+ city: formData.city,
+ state: formData.state,
+ country: formData.country,
+ postcode: formData.postcode,
+ },
+ };
+ await onSave(updateData);
+ } else {
+ const createData: CreateUserRequest = {
+ email: formData.email,
+ login: {
+ username: formData.username,
+ password: formData.password,
+ },
+ name: {
+ first: formData.firstName,
+ last: formData.lastName,
+ title: formData.title,
+ },
+ gender: formData.gender,
+ location: {
+ street: {
+ number: parseInt(formData.streetNumber) || 0,
+ name: formData.streetName,
+ },
+ city: formData.city,
+ state: formData.state,
+ country: formData.country,
+ postcode: formData.postcode,
+ },
+ };
+ await onSave(createData);
+ }
+ onClose();
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "An error occurred");
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ const handleClose = () => {
+ if (!isSubmitting) {
+ onClose();
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/crm/components/DeleteCustomerDialog.tsx b/src/crm/components/DeleteCustomerDialog.tsx
new file mode 100644
index 0000000..26f5603
--- /dev/null
+++ b/src/crm/components/DeleteCustomerDialog.tsx
@@ -0,0 +1,51 @@
+import * as React from "react";
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ CircularProgress,
+} from "@mui/material";
+import DeleteIcon from "@mui/icons-material/Delete";
+import { User } from "../services/usersApi";
+
+interface DeleteCustomerDialogProps {
+ open: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+ user: User | null;
+ loading?: boolean;
+}
+
+export default function DeleteCustomerDialog({
+ open,
+ onClose,
+ onConfirm,
+ user,
+ loading = false,
+}: DeleteCustomerDialogProps) {
+ return (
+
+ );
+}
diff --git a/src/crm/pages/Contacts.tsx b/src/crm/pages/Contacts.tsx
new file mode 100644
index 0000000..f9038c6
--- /dev/null
+++ b/src/crm/pages/Contacts.tsx
@@ -0,0 +1,17 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+
+export default function Contacts() {
+ return (
+
+
+ Contacts Page
+
+
+ This is the contacts management page where you can organize and manage
+ your business contacts.
+
+
+ );
+}
diff --git a/src/crm/pages/Customers.tsx b/src/crm/pages/Customers.tsx
new file mode 100644
index 0000000..9a8ef3d
--- /dev/null
+++ b/src/crm/pages/Customers.tsx
@@ -0,0 +1,481 @@
+import * as React from "react";
+import {
+ Box,
+ Typography,
+ Button,
+ TextField,
+ Card,
+ CardContent,
+ Stack,
+ Alert,
+ Snackbar,
+ Avatar,
+ Chip,
+ IconButton,
+ Menu,
+ MenuItem,
+ ListItemIcon,
+ ListItemText,
+} from "@mui/material";
+import {
+ DataGrid,
+ GridColDef,
+ GridRowParams,
+ GridToolbar,
+} from "@mui/x-data-grid";
+import AddIcon from "@mui/icons-material/Add";
+import SearchIcon from "@mui/icons-material/Search";
+import MoreVertIcon from "@mui/icons-material/MoreVert";
+import EditIcon from "@mui/icons-material/Edit";
+
+import PersonIcon from "@mui/icons-material/Person";
+import {
+ usersApi,
+ User,
+ CreateUserRequest,
+ UpdateUserRequest,
+} from "../services/usersApi";
+import CustomerModal from "../components/CustomerModal";
+import DeleteCustomerDialog from "../components/DeleteCustomerDialog";
+
+interface ActionMenuProps {
+ user: User;
+ onEdit: (user: User) => void;
+ onDelete: (user: User) => void;
+}
+
+function ActionMenu({ user, onEdit, onDelete }: ActionMenuProps) {
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event: React.MouseEvent) => {
+ event.stopPropagation();
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ const handleEdit = () => {
+ onEdit(user);
+ handleClose();
+ };
+
+ const handleDelete = () => {
+ onDelete(user);
+ handleClose();
+ };
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
+
+export default function Customers() {
+ const [users, setUsers] = React.useState([]);
+ const [loading, setLoading] = React.useState(true);
+ const [error, setError] = React.useState(null);
+ const [searchQuery, setSearchQuery] = React.useState("");
+ const [page, setPage] = React.useState(0);
+ const [pageSize, setPageSize] = React.useState(25);
+ const [totalRows, setTotalRows] = React.useState(0);
+
+ // Modal states
+ const [modalOpen, setModalOpen] = React.useState(false);
+ const [selectedUser, setSelectedUser] = React.useState(null);
+ const [modalLoading, setModalLoading] = React.useState(false);
+
+ // Delete confirmation states
+ const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
+ const [userToDelete, setUserToDelete] = React.useState(null);
+ const [deleting, setDeleting] = React.useState(false);
+
+ // Snackbar states
+ const [snackbar, setSnackbar] = React.useState<{
+ open: boolean;
+ message: string;
+ severity: "success" | "error";
+ }>({
+ open: false,
+ message: "",
+ severity: "success",
+ });
+
+ const showSnackbar = (
+ message: string,
+ severity: "success" | "error" = "success",
+ ) => {
+ setSnackbar({ open: true, message, severity });
+ };
+
+ const closeSnackbar = () => {
+ setSnackbar((prev) => ({ ...prev, open: false }));
+ };
+
+ const fetchUsers = React.useCallback(async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await usersApi.getUsers({
+ page: page + 1, // API uses 1-based pagination
+ perPage: pageSize,
+ search: searchQuery,
+ sortBy: "name.first",
+ });
+
+ setUsers(response.data);
+ setTotalRows(response.total);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to fetch users");
+ } finally {
+ setLoading(false);
+ }
+ }, [page, pageSize, searchQuery]);
+
+ React.useEffect(() => {
+ fetchUsers();
+ }, [fetchUsers]);
+
+ // Debounced search
+ const debouncedSearch = React.useMemo(() => {
+ const timer = setTimeout(() => {
+ setPage(0); // Reset to first page when searching
+ }, 300);
+ return () => clearTimeout(timer);
+ }, [searchQuery]);
+
+ const handleSearchChange = (event: React.ChangeEvent) => {
+ setSearchQuery(event.target.value);
+ };
+
+ const handleAddUser = () => {
+ setSelectedUser(null);
+ setModalOpen(true);
+ };
+
+ const handleEditUser = (user: User) => {
+ setSelectedUser(user);
+ setModalOpen(true);
+ };
+
+ const handleDeleteUser = (user: User) => {
+ setUserToDelete(user);
+ setDeleteDialogOpen(true);
+ };
+
+ const confirmDelete = async () => {
+ if (!userToDelete) return;
+
+ setDeleting(true);
+ try {
+ await usersApi.deleteUser(userToDelete.login.uuid);
+ showSnackbar("User deleted successfully");
+ fetchUsers();
+ } catch (err) {
+ showSnackbar(
+ err instanceof Error ? err.message : "Failed to delete user",
+ "error",
+ );
+ } finally {
+ setDeleting(false);
+ setDeleteDialogOpen(false);
+ setUserToDelete(null);
+ }
+ };
+
+ const handleSaveUser = async (
+ userData: CreateUserRequest | UpdateUserRequest,
+ ) => {
+ setModalLoading(true);
+ try {
+ if (selectedUser) {
+ // Update existing user
+ await usersApi.updateUser(
+ selectedUser.login.uuid,
+ userData as UpdateUserRequest,
+ );
+ showSnackbar("User updated successfully");
+ } else {
+ // Create new user
+ await usersApi.createUser(userData as CreateUserRequest);
+ showSnackbar("User created successfully");
+ }
+ fetchUsers();
+ } catch (err) {
+ throw err; // Let the modal handle the error
+ } finally {
+ setModalLoading(false);
+ }
+ };
+
+ const formatDate = (dateString: string) => {
+ return new Date(dateString).toLocaleDateString();
+ };
+
+ const getInitials = (first: string, last: string) => {
+ return `${first.charAt(0)}${last.charAt(0)}`.toUpperCase();
+ };
+
+ const columns: GridColDef[] = [
+ {
+ field: "avatar",
+ headerName: "",
+ width: 60,
+ sortable: false,
+ filterable: false,
+ align: "center",
+ headerAlign: "center",
+ renderCell: (params) => {
+ const user = params.row as User;
+ return (
+
+ {user.picture?.thumbnail ? (
+
+ ) : (
+ getInitials(user.name.first, user.name.last)
+ )}
+
+ );
+ },
+ },
+ {
+ field: "name",
+ headerName: "Name",
+ width: 200,
+ valueGetter: (value, row: User) =>
+ `${row.name.title} ${row.name.first} ${row.name.last}`,
+ },
+ {
+ field: "email",
+ headerName: "Email",
+ width: 250,
+ valueGetter: (value, row: User) => row.email,
+ },
+ {
+ field: "username",
+ headerName: "Username",
+ width: 150,
+ valueGetter: (value, row: User) => row.login.username,
+ },
+ {
+ field: "location",
+ headerName: "Location",
+ width: 200,
+ valueGetter: (value, row: User) =>
+ `${row.location.city}, ${row.location.country}`,
+ },
+ {
+ field: "phone",
+ headerName: "Phone",
+ width: 150,
+ valueGetter: (value, row: User) => row.phone,
+ },
+ {
+ field: "gender",
+ headerName: "Gender",
+ width: 100,
+ renderCell: (params) => {
+ const user = params.row as User;
+ return (
+
+ );
+ },
+ },
+ {
+ field: "age",
+ headerName: "Age",
+ width: 80,
+ valueGetter: (value, row: User) => row.dob.age,
+ },
+ {
+ field: "registered",
+ headerName: "Registered",
+ width: 120,
+ valueGetter: (value, row: User) => formatDate(row.registered.date),
+ },
+ {
+ field: "actions",
+ headerName: "Actions",
+ width: 80,
+ sortable: false,
+ filterable: false,
+ renderCell: (params) => {
+ const user = params.row as User;
+ return (
+
+ );
+ },
+ },
+ ];
+
+ return (
+
+
+ Customer Management
+
+
+
+
+
+ Customers
+
+
+ ),
+ }}
+ sx={{ minWidth: 200 }}
+ />
+ }
+ onClick={handleAddUser}
+ >
+ Add Customer
+
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+ setPage(newPage)}
+ onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
+ pageSizeOptions={[10, 25, 50, 100]}
+ loading={loading}
+ getRowId={(row) => row.login.uuid}
+ density="comfortable"
+ disableRowSelectionOnClick
+ rowHeight={56}
+ slots={{
+ toolbar: GridToolbar,
+ }}
+ slotProps={{
+ toolbar: {
+ showQuickFilter: false,
+ },
+ }}
+ sx={{
+ "& .MuiDataGrid-cell": {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ },
+ '& .MuiDataGrid-cell[data-field="avatar"]': {
+ justifyContent: "center",
+ },
+ }}
+ />
+
+
+
+
+ {/* Add/Edit Customer Modal */}
+ setModalOpen(false)}
+ onSave={handleSaveUser}
+ user={selectedUser}
+ loading={modalLoading}
+ />
+
+ {/* Delete Confirmation Dialog */}
+ setDeleteDialogOpen(false)}
+ onConfirm={confirmDelete}
+ user={userToDelete}
+ loading={deleting}
+ />
+
+ {/* Success/Error Snackbar */}
+
+
+ {snackbar.message}
+
+
+
+ );
+}
diff --git a/src/crm/pages/Deals.tsx b/src/crm/pages/Deals.tsx
new file mode 100644
index 0000000..1aa0eac
--- /dev/null
+++ b/src/crm/pages/Deals.tsx
@@ -0,0 +1,17 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+
+export default function Deals() {
+ return (
+
+
+ Deals Page
+
+
+ This is the deals management page where you can track and manage your
+ sales pipeline.
+
+
+ );
+}
diff --git a/src/crm/pages/Reports.tsx b/src/crm/pages/Reports.tsx
new file mode 100644
index 0000000..85682cc
--- /dev/null
+++ b/src/crm/pages/Reports.tsx
@@ -0,0 +1,17 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+
+export default function Reports() {
+ return (
+
+
+ Reports Page
+
+
+ This is the reports page where you can access and generate various
+ analytics and insights.
+
+
+ );
+}
diff --git a/src/crm/pages/Settings.tsx b/src/crm/pages/Settings.tsx
new file mode 100644
index 0000000..5ccf1a6
--- /dev/null
+++ b/src/crm/pages/Settings.tsx
@@ -0,0 +1,17 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+
+export default function Settings() {
+ return (
+
+
+ Settings Page
+
+
+ This is the settings page where you can configure your CRM preferences
+ and manage your account.
+
+
+ );
+}
diff --git a/src/crm/pages/Tasks.tsx b/src/crm/pages/Tasks.tsx
new file mode 100644
index 0000000..3f4971d
--- /dev/null
+++ b/src/crm/pages/Tasks.tsx
@@ -0,0 +1,17 @@
+import * as React from "react";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+
+export default function Tasks() {
+ return (
+
+
+ Tasks Page
+
+
+ This is the tasks management page where you can track all your
+ activities and follow-ups.
+
+
+ );
+}
diff --git a/src/crm/services/usersApi.ts b/src/crm/services/usersApi.ts
new file mode 100644
index 0000000..ce4c1e3
--- /dev/null
+++ b/src/crm/services/usersApi.ts
@@ -0,0 +1,197 @@
+// Types for User API
+export interface UserLocation {
+ street: {
+ number: number;
+ name: string;
+ };
+ city: string;
+ state: string;
+ country: string;
+ postcode: string;
+ coordinates: {
+ latitude: number;
+ longitude: number;
+ };
+ timezone: {
+ offset: string;
+ description: string;
+ };
+}
+
+export interface UserName {
+ title: string;
+ first: string;
+ last: string;
+}
+
+export interface UserLogin {
+ uuid: string;
+ username: string;
+ password: string;
+}
+
+export interface UserPicture {
+ large: string;
+ medium: string;
+ thumbnail: string;
+}
+
+export interface User {
+ login: UserLogin;
+ name: UserName;
+ gender: string;
+ location: UserLocation;
+ email: string;
+ dob: {
+ date: string;
+ age: number;
+ };
+ registered: {
+ date: string;
+ age: number;
+ };
+ phone: string;
+ cell: string;
+ picture: UserPicture;
+ nat: string;
+}
+
+export interface UsersApiResponse {
+ page: number;
+ perPage: number;
+ total: number;
+ span: string;
+ effectivePage: number;
+ data: User[];
+}
+
+export interface CreateUserRequest {
+ email: string;
+ login: {
+ username: string;
+ password?: string;
+ };
+ name: {
+ first: string;
+ last: string;
+ title?: string;
+ };
+ gender?: string;
+ location?: Partial;
+}
+
+export interface UpdateUserRequest {
+ email?: string;
+ name?: Partial;
+ location?: Partial;
+ phone?: string;
+ cell?: string;
+ gender?: string;
+}
+
+const BASE_URL = "https://user-api.builder-io.workers.dev/api";
+
+export class UsersApiService {
+ async getUsers(
+ params: {
+ page?: number;
+ perPage?: number;
+ search?: string;
+ sortBy?: string;
+ span?: string;
+ } = {},
+ ): Promise {
+ const queryParams = new URLSearchParams();
+
+ if (params.page) queryParams.append("page", params.page.toString());
+ if (params.perPage)
+ queryParams.append("perPage", params.perPage.toString());
+ if (params.search) queryParams.append("search", params.search);
+ if (params.sortBy) queryParams.append("sortBy", params.sortBy);
+ if (params.span) queryParams.append("span", params.span);
+
+ const response = await fetch(`${BASE_URL}/users?${queryParams}`);
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch users: ${response.statusText}`);
+ }
+
+ return response.json();
+ }
+
+ async getUserById(id: string): Promise {
+ const response = await fetch(`${BASE_URL}/users/${encodeURIComponent(id)}`);
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch user: ${response.statusText}`);
+ }
+
+ return response.json();
+ }
+
+ async createUser(
+ userData: CreateUserRequest,
+ ): Promise<{ success: boolean; uuid: string; message: string }> {
+ const response = await fetch(`${BASE_URL}/users`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(userData),
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(
+ error.error || `Failed to create user: ${response.statusText}`,
+ );
+ }
+
+ return response.json();
+ }
+
+ async updateUser(
+ id: string,
+ userData: UpdateUserRequest,
+ ): Promise<{ success: boolean; message: string }> {
+ const response = await fetch(
+ `${BASE_URL}/users/${encodeURIComponent(id)}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(userData),
+ },
+ );
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(
+ error.error || `Failed to update user: ${response.statusText}`,
+ );
+ }
+
+ return response.json();
+ }
+
+ async deleteUser(id: string): Promise<{ success: boolean; message: string }> {
+ const response = await fetch(
+ `${BASE_URL}/users/${encodeURIComponent(id)}`,
+ {
+ method: "DELETE",
+ },
+ );
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(
+ error.error || `Failed to delete user: ${response.statusText}`,
+ );
+ }
+
+ return response.json();
+ }
+}
+
+export const usersApi = new UsersApiService();
diff --git a/src/shared-theme/AppTheme.tsx b/src/shared-theme/AppTheme.tsx
index b8e5b3a..e7b51c4 100644
--- a/src/shared-theme/AppTheme.tsx
+++ b/src/shared-theme/AppTheme.tsx
@@ -28,6 +28,7 @@ export default function AppTheme(props: AppThemeProps) {
colorSchemeSelector: "data-mui-color-scheme",
cssVarPrefix: "template",
},
+ defaultColorScheme: "light", // Set light mode as default instead of using system preference
colorSchemes, // Recently added in v6 for building light & dark mode app, see https://mui.com/material-ui/customization/palette/#color-schemes
typography,
shadows,