From 184353e29fc04fdbd257567ba7299c3165de4887 Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Thu, 30 Jan 2025 16:13:53 +0200 Subject: [PATCH 01/23] created-crm-page --- .../brainspace/components/Modules.tsx | 39 +++++++++++-------- src/app/[locale]/crm/components/Test.tsx | 7 ++++ src/app/[locale]/crm/page.tsx | 12 ++++++ 3 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 src/app/[locale]/crm/components/Test.tsx create mode 100644 src/app/[locale]/crm/page.tsx diff --git a/src/app/[locale]/brainspace/components/Modules.tsx b/src/app/[locale]/brainspace/components/Modules.tsx index 4b9fcab..55f0e0b 100644 --- a/src/app/[locale]/brainspace/components/Modules.tsx +++ b/src/app/[locale]/brainspace/components/Modules.tsx @@ -14,7 +14,9 @@ import Loader from "@/common/loader/loader"; import { Swiper, SwiperSlide } from "swiper/react"; import { Navigation, Pagination } from "swiper/modules"; -const Modules: React.FC<{ additionalModules?: Module[] }> = ({ additionalModules = [] }) => { +const Modules: React.FC<{ additionalModules?: Module[] }> = ({ + additionalModules = [], +}) => { const [loading, setLoading] = useState(true); const [modulesData, setModulesData] = useState([]); const { accessToken } = useAuth(); @@ -23,12 +25,15 @@ const Modules: React.FC<{ additionalModules?: Module[] }> = ({ additionalModules // Function to fetch modules const fetchModules = async () => { try { - const response = await fetch("https://posnew.smcare.net/v0_0_1-modules/get-modules", { - headers: { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - }); + const response = await fetch( + "https://posnew.smcare.net/v0_0_1-modules/get-modules", + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + } + ); if (response.ok) { const data = await response.json(); @@ -73,16 +78,14 @@ const Modules: React.FC<{ additionalModules?: Module[] }> = ({ additionalModules if (width >= 1100) { setChunkSize(10); // Large screens (lg) } else if (width >= 800) { - setChunkSize(9); // Medium screens (md) - } - else if (width >= 700) { + setChunkSize(9); // Medium screens (md) + } else if (width >= 700) { setChunkSize(6); // Medium screens (md) } else if (width >= 400) { setChunkSize(4); // Medium screens (md) - } else if ((width >= 300 && width <= 500) && height >= 800) { + } else if (width >= 300 && width <= 500 && height >= 800) { setChunkSize(4); // Small screens - } - else { + } else { setChunkSize(2); // Small screens (sm) } // if (height >= 1100) { @@ -92,7 +95,7 @@ const Modules: React.FC<{ additionalModules?: Module[] }> = ({ additionalModules // } else if (height <= 800 && width >= 500) { // setChunkSize(2); // Small screens // } else if (height >= 800) { - // setChunkSize(9); // Medium screens (md) + // setChunkSize(9); // Medium screens (md) // } else if (height >= 700) { // setChunkSize(6); // Medium screens (md) // } else if (height >= 400) { @@ -136,7 +139,9 @@ const Modules: React.FC<{ additionalModules?: Module[] }> = ({ additionalModules
{chunk.map((module: Module) => ( @@ -148,7 +153,9 @@ const Modules: React.FC<{ additionalModules?: Module[] }> = ({ additionalModules width={160} height={160} /> -

{module.name}

+

+ {module.name} +

))} diff --git a/src/app/[locale]/crm/components/Test.tsx b/src/app/[locale]/crm/components/Test.tsx new file mode 100644 index 0000000..5d97e7b --- /dev/null +++ b/src/app/[locale]/crm/components/Test.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Test = () => { + return
Hello From CRM
; +}; + +export default Test; diff --git a/src/app/[locale]/crm/page.tsx b/src/app/[locale]/crm/page.tsx new file mode 100644 index 0000000..37eede8 --- /dev/null +++ b/src/app/[locale]/crm/page.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import Test from "./components/Test"; + +const Page = () => { + return ( +
+ +
+ ); +}; + +export default Page; From c41efe38c06af6acd1b7a396bc7e95f6c2dc9456 Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Mon, 3 Feb 2025 13:07:26 +0200 Subject: [PATCH 02/23] created-leads-kanban --- package-lock.json | 56 ++ package.json | 3 + src/app/[locale]/crm/components/Lead.tsx | 47 ++ src/app/[locale]/crm/components/Leads.tsx | 48 ++ .../crm/components/StatusContainer.tsx | 97 ++++ src/app/[locale]/crm/components/Test.tsx | 7 - src/app/[locale]/crm/page.tsx | 8 +- src/app/page.tsx | 4 +- src/redux/slices/posSlice.ts | 517 +++++++++--------- 9 files changed, 518 insertions(+), 269 deletions(-) create mode 100644 src/app/[locale]/crm/components/Lead.tsx create mode 100644 src/app/[locale]/crm/components/Leads.tsx create mode 100644 src/app/[locale]/crm/components/StatusContainer.tsx delete mode 100644 src/app/[locale]/crm/components/Test.tsx diff --git a/package-lock.json b/package-lock.json index 2605f42..04bb2b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "brainspace", "version": "0.1.0", "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@reduxjs/toolkit": "^2.5.0", "@tsparticles/engine": "^3.7.1", "axios": "^1.7.9", @@ -84,6 +87,59 @@ "node": ">=6.9.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", diff --git a/package.json b/package.json index 0207951..7973000 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "lint": "next lint" }, "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@reduxjs/toolkit": "^2.5.0", "@tsparticles/engine": "^3.7.1", "axios": "^1.7.9", diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx new file mode 100644 index 0000000..5008996 --- /dev/null +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { useDraggable } from "@dnd-kit/core"; +import { CSS } from "@dnd-kit/utilities"; + +interface LeadType { + id: number; + status: string; + title: string; + price: number; + name: string; + number: string; +} + +interface LeadProps { + lead: LeadType; + color: string; +} + +const Lead: React.FC = ({ lead, color }) => { + const { attributes, listeners, setNodeRef, isDragging, transform } = + useDraggable({ + id: lead.id.toString(), + }); + + const style = { + transform: CSS.Translate.toString(transform), + }; + + return ( +
  • +

    {lead.title}

    +

    Price: ${lead.price}

    +

    Contact: {lead.name}

    +

    {lead.number}

    +
  • + ); +}; + +export default Lead; diff --git a/src/app/[locale]/crm/components/Leads.tsx b/src/app/[locale]/crm/components/Leads.tsx new file mode 100644 index 0000000..8637184 --- /dev/null +++ b/src/app/[locale]/crm/components/Leads.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import Lead from "./Lead"; +import { useDroppable } from "@dnd-kit/core"; + +interface Lead { + id: number; + status: string; + title: string; + price: number; + name: string; + number: string; +} + +interface LeadsProps { + status: string; + leads: Lead[]; +} + +const statusColors: Record = { + new: "red-500", + qualified: "blue-500", + proposition: "purple-500", + won: "green-500", +}; + +const Leads: React.FC = ({ status, leads }) => { + const { setNodeRef } = useDroppable({ + id: status, + }); + + return ( +
    +

    + {status.toUpperCase()} +

    +
      + {leads.map((lead) => ( + + ))} +
    +
    + ); +}; + +export default Leads; diff --git a/src/app/[locale]/crm/components/StatusContainer.tsx b/src/app/[locale]/crm/components/StatusContainer.tsx new file mode 100644 index 0000000..9e1db10 --- /dev/null +++ b/src/app/[locale]/crm/components/StatusContainer.tsx @@ -0,0 +1,97 @@ +"use client"; + +import React, { useState } from "react"; +import Leads from "./Leads"; +import { DndContext, DragEndEvent } from "@dnd-kit/core"; + +interface Lead { + id: number; + status: string; + title: string; + price: number; + name: string; + number: string; +} + +const StatusContainer: React.FC = () => { + const status: string[] = ["new", "qualified", "proposition", "won"]; + + const [leads, setLeads] = useState([ + { + id: 1, + status: "new", + title: "beeflex1", + price: 10000, + name: "contact name", + number: "+96176123456", + }, + { + id: 2, + status: "new", + title: "beeflex2", + price: 10000, + name: "contact name", + number: "+96176123456", + }, + { + id: 3, + status: "qualified", + title: "beeflex3", + price: 10000, + name: "contact name", + number: "+96176123456", + }, + { + id: 4, + status: "qualified", + title: "beeflex4", + price: 10000, + name: "contact name", + number: "+96176123456", + }, + { + id: 5, + status: "proposition", + title: "beeflex5", + price: 10000, + name: "contact name", + number: "+96176123456", + }, + { + id: 6, + status: "won", + title: "beeflex6", + price: 10000, + name: "contact name", + number: "+96176123456", + }, + ]); + + const onDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + const updatedLeads: Lead[] = leads.map((lead) => { + if (lead.id === +active.id) { + return { ...lead, status: over?.id as string }; + } + return lead; + }); + setLeads(updatedLeads); + }; + + return ( + +
    + {status.map((s, i) => ( + lead.status === s)} + /> + ))} +
    +
    + ); +}; + +export default StatusContainer; diff --git a/src/app/[locale]/crm/components/Test.tsx b/src/app/[locale]/crm/components/Test.tsx deleted file mode 100644 index 5d97e7b..0000000 --- a/src/app/[locale]/crm/components/Test.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const Test = () => { - return
    Hello From CRM
    ; -}; - -export default Test; diff --git a/src/app/[locale]/crm/page.tsx b/src/app/[locale]/crm/page.tsx index 37eede8..e8f1e6d 100644 --- a/src/app/[locale]/crm/page.tsx +++ b/src/app/[locale]/crm/page.tsx @@ -1,12 +1,8 @@ import React from "react"; -import Test from "./components/Test"; +import StatusContainer from "./components/StatusContainer"; const Page = () => { - return ( -
    - -
    - ); + return ; }; export default Page; diff --git a/src/app/page.tsx b/src/app/page.tsx index 915fdd8..77d15e3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ -import { redirect } from 'next/navigation'; +import { redirect } from "next/navigation"; export default function RootPage() { - redirect('/en'); + redirect("/en"); } diff --git a/src/redux/slices/posSlice.ts b/src/redux/slices/posSlice.ts index 61b696c..7b95fa0 100644 --- a/src/redux/slices/posSlice.ts +++ b/src/redux/slices/posSlice.ts @@ -1,11 +1,10 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { Product, InvoiceDraft } from "@/types/PosProTypes"; import { formatNumber } from "@/utils/formatNumber"; -import { createSelector } from 'reselect'; +import { createSelector } from "reselect"; import type { WritableDraft } from "immer"; - -type CartItem = Product & { quantity: number ; subtotal: number; }; +type CartItem = Product & { quantity: number; subtotal: number }; type POSState = { cart: CartItem[]; @@ -14,31 +13,28 @@ type POSState = { activeInvoice: InvoiceDraft | null; }; - - const initialState: POSState = { cart: [], selectedRow: null, - invoiceDrafts: [], + invoiceDrafts: [], activeInvoice: null, }; - - - - const posSlice = createSlice({ name: "pos", initialState, reducers: { - addProductToCart(state, action: PayloadAction<{ product: Product; quantity: number }>) { + addProductToCart( + state, + action: PayloadAction<{ product: Product; quantity: number }> + ) { const { product, quantity } = action.payload; - + if (!state.activeInvoice) return; - + const priceCategory = state.activeInvoice.priceCategory || 1; // Default price category const currencyRate = state.activeInvoice.selectedCurrency?.usdtocurr || 1; // Default to 1 if no currency rate - + // Calculate subtotal for a cart item const calculateSubtotal = (item: CartItem) => { const price = (item.prices[priceCategory] || 0) * currencyRate; // Adjust price by category and currency @@ -46,9 +42,11 @@ const posSlice = createSlice({ const discount = item.percentDisc || 0; return item.quantity * price * (1 + vat / 100) * (1 - discount / 100); }; - + // Check if the product already exists in the cart - const existingCartItem = state.activeInvoice.cart.find((item) => item.code === product.code); + const existingCartItem = state.activeInvoice.cart.find( + (item) => item.code === product.code + ); if (existingCartItem) { existingCartItem.quantity += quantity; // Update quantity existingCartItem.subtotal = calculateSubtotal(existingCartItem); // Recalculate subtotal @@ -61,19 +59,19 @@ const posSlice = createSlice({ }; state.activeInvoice.cart.push(newCartItem); } - + // Recalculate total items state.activeInvoice.totalItems = state.activeInvoice.cart.reduce( (total, item) => total + item.quantity, 0 ); - + // Recalculate total price state.activeInvoice.totalPrice = state.activeInvoice.cart.reduce( (total, item) => total + item.subtotal, 0 ); - + // Update the corresponding draft in invoiceDrafts const existingDraftIndex = state.invoiceDrafts.findIndex( (draft) => draft.draftKey === state.activeInvoice?.draftKey @@ -81,60 +79,66 @@ const posSlice = createSlice({ if (existingDraftIndex !== -1) { state.invoiceDrafts[existingDraftIndex] = { ...state.activeInvoice }; } - } - -, - - - - updateCartQuantity(state, action: PayloadAction<{ code: string; quantity: number }>) { - const item = state.cart.find(cartItem => cartItem.code === action.payload.code); + }, + + updateCartQuantity( + state, + action: PayloadAction<{ code: string; quantity: number }> + ) { + const item = state.cart.find( + (cartItem) => cartItem.code === action.payload.code + ); if (item) { item.quantity = action.payload.quantity; } }, removeProductFromCart(state, action: PayloadAction) { const productCode = action.payload; - + // Update the cart in the root state - state.cart = state.cart.filter(item => item.code !== productCode); - + state.cart = state.cart.filter((item) => item.code !== productCode); + // Check if activeInvoice is not null before updating it if (state.activeInvoice) { - state.activeInvoice.cart = state.activeInvoice.cart.filter(item => item.code !== productCode); - + state.activeInvoice.cart = state.activeInvoice.cart.filter( + (item) => item.code !== productCode + ); + // Recalculate totals state.activeInvoice.totalItems = state.activeInvoice.cart.reduce( (total, item) => total + item.quantity, 0 ); state.activeInvoice.totalPrice = state.activeInvoice.cart.reduce( - (total, item) => total + item.quantity * (item.prices[state.activeInvoice?.priceCategory || 1] || 0), + (total, item) => + total + + item.quantity * + (item.prices[state.activeInvoice?.priceCategory || 1] || 0), 0 ); } - } -, + }, setSelectedRow(state, action: PayloadAction) { state.selectedRow = action.payload; }, - - - updateSelectedRow(state, action: PayloadAction<{ field: string; value: number }>) { + updateSelectedRow( + state, + action: PayloadAction<{ field: string; value: number }> + ) { if (!state.selectedRow || !state.activeInvoice) { console.warn("No selected row or active invoice."); return; } - + const { field, value } = action.payload; const priceCategory = state.activeInvoice.priceCategory || 1; const usdtocurr = state.activeInvoice.selectedCurrency?.usdtocurr || 1; - + const cartItem = state.activeInvoice.cart.find( (item) => item.code === state.selectedRow?.code ); - + if (cartItem) { if (field === "price") { cartItem.prices[priceCategory] = value / usdtocurr; @@ -143,267 +147,278 @@ const posSlice = createSlice({ (cartItem as any)[field] = value; (state.selectedRow as any)[field] = value; } - + // Recalculate subtotal for the row const price = (cartItem.prices[priceCategory] || 0) * usdtocurr; const vat = cartItem.vat || 0; const discount = cartItem.percentDisc || 0; - cartItem.subtotal = cartItem.quantity * price * (1 + vat / 100) * (1 - discount / 100); + cartItem.subtotal = + cartItem.quantity * price * (1 + vat / 100) * (1 - discount / 100); state.selectedRow.subtotal = cartItem.subtotal; // Update selected row's subtotal } - + // Recalculate total price - state.activeInvoice.totalPrice = state.activeInvoice.cart.reduce((total, item) => total + item.subtotal, 0); - + state.activeInvoice.totalPrice = state.activeInvoice.cart.reduce( + (total, item) => total + item.subtotal, + 0 + ); + // Trigger a re-render by re-assigning the cart array state.activeInvoice.cart = [...state.activeInvoice.cart]; - } -, - - - - + }, createDraft(state, action: PayloadAction) { state.invoiceDrafts.push(action.payload); state.activeInvoice = action.payload; state.cart = []; // Clear cart for the new invoice }, - + saveDraft(state, action: PayloadAction) { - const existingDraftIndex = state.invoiceDrafts.findIndex( - (draft) => draft.draftKey === action.payload.draftKey - ); - - if (existingDraftIndex !== -1) { - // Update the existing draft - state.invoiceDrafts[existingDraftIndex] = action.payload; - } else { - // Add a new draft - state.invoiceDrafts.push(action.payload); - } + const existingDraftIndex = state.invoiceDrafts.findIndex( + (draft) => draft.draftKey === action.payload.draftKey + ); + + if (existingDraftIndex !== -1) { + // Update the existing draft + state.invoiceDrafts[existingDraftIndex] = action.payload; + } else { + // Add a new draft + state.invoiceDrafts.push(action.payload); + } }, - // Delete a specific draft - deleteDraft(state, action: PayloadAction) { - state.invoiceDrafts = state.invoiceDrafts.filter( - (draft) => draft.draftKey !== action.payload - ); - }, + // Delete a specific draft + deleteDraft(state, action: PayloadAction) { + state.invoiceDrafts = state.invoiceDrafts.filter( + (draft) => draft.draftKey !== action.payload + ); + }, - updateInvoiceCurrency(state, action: PayloadAction<{ id: number; usdtocurr: number }>) { - if (!state.activeInvoice) return; - - const activeInvoice = state.activeInvoice; - activeInvoice.selectedCurrency = action.payload; - - const priceCategory = activeInvoice.priceCategory; - const usdtocurr = action.payload.usdtocurr; - - // Update subtotals in the cart - activeInvoice.cart.forEach((item) => { - const price = (item.prices[priceCategory] || 0) * usdtocurr; - const vat = item.vat || 0; - const discount = item.percentDisc || 0; - - item.subtotal = item.quantity * price * (1 + vat / 100) * (1 - discount / 100); - }); - - // Recalculate the total price - activeInvoice.totalPrice = activeInvoice.cart.reduce((total, item) => total + item.subtotal, 0); - - // Update the corresponding draft in invoiceDrafts - const existingDraftIndex = state.invoiceDrafts.findIndex( - (draft) => draft.draftKey === activeInvoice.draftKey - ); - if (existingDraftIndex !== -1) { - state.invoiceDrafts[existingDraftIndex].selectedCurrency = action.payload; - } - }, - + updateInvoiceCurrency( + state, + action: PayloadAction<{ id: number; usdtocurr: number }> + ) { + if (!state.activeInvoice) return; + + const activeInvoice = state.activeInvoice; + activeInvoice.selectedCurrency = action.payload; + + const priceCategory = activeInvoice.priceCategory; + const usdtocurr = action.payload.usdtocurr; + + // Update subtotals in the cart + activeInvoice.cart.forEach((item) => { + const price = (item.prices[priceCategory] || 0) * usdtocurr; + const vat = item.vat || 0; + const discount = item.percentDisc || 0; + + item.subtotal = + item.quantity * price * (1 + vat / 100) * (1 - discount / 100); + }); + + // Recalculate the total price + activeInvoice.totalPrice = activeInvoice.cart.reduce( + (total, item) => total + item.subtotal, + 0 + ); - updateInvoiceCustomer(state, action: PayloadAction) { - if (state.activeInvoice) { - state.activeInvoice.customer = action.payload; - // Update the corresponding draft in invoiceDrafts const existingDraftIndex = state.invoiceDrafts.findIndex( - (draft) => draft.draftKey === state.activeInvoice?.draftKey + (draft) => draft.draftKey === activeInvoice.draftKey ); if (existingDraftIndex !== -1) { - state.invoiceDrafts[existingDraftIndex].customer = action.payload; + state.invoiceDrafts[existingDraftIndex].selectedCurrency = + action.payload; } - } - }, - - updateInvoiceType(state, action: PayloadAction) { - if (state.activeInvoice) { - state.activeInvoice.invoiceType = action.payload; - + }, + + updateInvoiceCustomer(state, action: PayloadAction) { + if (state.activeInvoice) { + state.activeInvoice.customer = action.payload; + + // Update the corresponding draft in invoiceDrafts + const existingDraftIndex = state.invoiceDrafts.findIndex( + (draft) => draft.draftKey === state.activeInvoice?.draftKey + ); + if (existingDraftIndex !== -1) { + state.invoiceDrafts[existingDraftIndex].customer = action.payload; + } + } + }, + + updateInvoiceType(state, action: PayloadAction) { + if (state.activeInvoice) { + state.activeInvoice.invoiceType = action.payload; + + // Update the corresponding draft in invoiceDrafts + const existingDraftIndex = state.invoiceDrafts.findIndex( + (draft) => draft.draftKey === state.activeInvoice?.draftKey + ); + if (existingDraftIndex !== -1) { + state.invoiceDrafts[existingDraftIndex].invoiceType = action.payload; + } + } + }, + + updateInvoiceInternalNote(state, action: PayloadAction) { + if (!state.activeInvoice) { + console.warn("No active invoice found."); + return; + } + + // Update the active invoice's internal note + state.activeInvoice.internalNote = action.payload; + // Update the corresponding draft in invoiceDrafts const existingDraftIndex = state.invoiceDrafts.findIndex( (draft) => draft.draftKey === state.activeInvoice?.draftKey ); if (existingDraftIndex !== -1) { - state.invoiceDrafts[existingDraftIndex].invoiceType = action.payload; + state.invoiceDrafts[existingDraftIndex].internalNote = action.payload; } - } - }, + }, - updateInvoiceInternalNote(state, action: PayloadAction) { - if (!state.activeInvoice) { - console.warn("No active invoice found."); - return; - } - - // Update the active invoice's internal note - state.activeInvoice.internalNote = action.payload; - - // Update the corresponding draft in invoiceDrafts - const existingDraftIndex = state.invoiceDrafts.findIndex( - (draft) => draft.draftKey === state.activeInvoice?.draftKey - ); - if (existingDraftIndex !== -1) { - state.invoiceDrafts[existingDraftIndex].internalNote = action.payload; - } - }, + updateInvoiceGeneralNote(state, action: PayloadAction) { + if (!state.activeInvoice) { + console.warn("No active invoice found."); + return; + } - updateInvoiceGeneralNote(state, action: PayloadAction) { - if (!state.activeInvoice) { - console.warn("No active invoice found."); - return; - } - - // Update the active invoice's general note - state.activeInvoice.generalNote = action.payload; - - // Update the corresponding draft in invoiceDrafts - const existingDraftIndex = state.invoiceDrafts.findIndex( - (draft) => draft.draftKey === state.activeInvoice?.draftKey - ); - if (existingDraftIndex !== -1) { - state.invoiceDrafts[existingDraftIndex].generalNote = action.payload; - } - }, - - - - // Set the active invoice - setActiveInvoice(state, action: PayloadAction) { - const draftKey = action.payload; - - // Save the current active invoice's state to its draft - if (state.activeInvoice) { + // Update the active invoice's general note + state.activeInvoice.generalNote = action.payload; + + // Update the corresponding draft in invoiceDrafts const existingDraftIndex = state.invoiceDrafts.findIndex( (draft) => draft.draftKey === state.activeInvoice?.draftKey ); if (existingDraftIndex !== -1) { - // Update the existing draft with the current active invoice's data - state.invoiceDrafts[existingDraftIndex] = { ...state.activeInvoice }; + state.invoiceDrafts[existingDraftIndex].generalNote = action.payload; } - } - - // Set the new active invoice - const newActiveDraft = state.invoiceDrafts.find((draft) => draft.draftKey === draftKey); - if (newActiveDraft) { - state.activeInvoice = { ...newActiveDraft }; - } - } -, + }, -updateInvoicePriceCategory(state, action: PayloadAction) { - if (!state.activeInvoice) return; + // Set the active invoice + setActiveInvoice(state, action: PayloadAction) { + const draftKey = action.payload; - const activeInvoice = state.activeInvoice; - activeInvoice.priceCategory = action.payload; + // Save the current active invoice's state to its draft + if (state.activeInvoice) { + const existingDraftIndex = state.invoiceDrafts.findIndex( + (draft) => draft.draftKey === state.activeInvoice?.draftKey + ); + if (existingDraftIndex !== -1) { + // Update the existing draft with the current active invoice's data + state.invoiceDrafts[existingDraftIndex] = { ...state.activeInvoice }; + } + } - const priceCategory = action.payload; - const usdtocurr = activeInvoice.selectedCurrency?.usdtocurr || 1; + // Set the new active invoice + const newActiveDraft = state.invoiceDrafts.find( + (draft) => draft.draftKey === draftKey + ); + if (newActiveDraft) { + state.activeInvoice = { ...newActiveDraft }; + } + }, + updateInvoicePriceCategory(state, action: PayloadAction) { + if (!state.activeInvoice) return; - // Update subtotals in the cart - activeInvoice.cart.forEach((item) => { - const price = (item.prices[priceCategory] || 0) * usdtocurr; - const vat = item.vat || 0; - const discount = item.percentDisc || 0; + const activeInvoice = state.activeInvoice; + activeInvoice.priceCategory = action.payload; - item.subtotal = item.quantity * price * (1 + vat / 100) * (1 - discount / 100); - }); + const priceCategory = action.payload; + const usdtocurr = activeInvoice.selectedCurrency?.usdtocurr || 1; - // Recalculate the total price - activeInvoice.totalPrice = activeInvoice.cart.reduce((total, item) => total + item.subtotal, 0); + // Update subtotals in the cart + activeInvoice.cart.forEach((item) => { + const price = (item.prices[priceCategory] || 0) * usdtocurr; + const vat = item.vat || 0; + const discount = item.percentDisc || 0; - // Update the corresponding draft in invoiceDrafts - const existingDraftIndex = state.invoiceDrafts.findIndex( - (draft) => draft.draftKey === activeInvoice.draftKey - ); - if (existingDraftIndex !== -1) { - state.invoiceDrafts[existingDraftIndex].priceCategory = action.payload; - } -}, - - updateInvoiceDiscount(state, action: PayloadAction) { - if (!state.activeInvoice) { - console.warn("No active invoice found."); - return; - } - - state.activeInvoice.discount = action.payload; - - // Calculate the discounted total immediately - const { usdtocurr } = state.activeInvoice.selectedCurrency || { usdtocurr: 1 }; - const priceCategory = state.activeInvoice.priceCategory; - - const rawTotal = state.activeInvoice.cart.reduce((total, item) => { - const price = (item.prices[priceCategory] || 0) * usdtocurr; - const vat = item.vat || 0; - const discount = item.percentDisc || 0; - const subtotal = item.quantity * price * (1 + vat / 100) * (1 - discount / 100); - return total + subtotal; - }, 0); - - state.activeInvoice.totalPrice = rawTotal * (1 - action.payload / 100); // Apply the discount - }, - - - - - // Clear the active invoice - clearActiveInvoice(state) { - state.activeInvoice = null; - state.cart = []; // Clear the cart when the invoice is cleared + item.subtotal = + item.quantity * price * (1 + vat / 100) * (1 - discount / 100); + }); + + // Recalculate the total price + activeInvoice.totalPrice = activeInvoice.cart.reduce( + (total, item) => total + item.subtotal, + 0 + ); + + // Update the corresponding draft in invoiceDrafts + const existingDraftIndex = state.invoiceDrafts.findIndex( + (draft) => draft.draftKey === activeInvoice.draftKey + ); + if (existingDraftIndex !== -1) { + state.invoiceDrafts[existingDraftIndex].priceCategory = action.payload; + } + }, + + updateInvoiceDiscount(state, action: PayloadAction) { + if (!state.activeInvoice) { + console.warn("No active invoice found."); + return; + } + + state.activeInvoice.discount = action.payload; + + // Calculate the discounted total immediately + const { usdtocurr } = state.activeInvoice.selectedCurrency || { + usdtocurr: 1, + }; + const priceCategory = state.activeInvoice.priceCategory; + + const rawTotal = state.activeInvoice.cart.reduce((total, item) => { + const price = (item.prices[priceCategory] || 0) * usdtocurr; + const vat = item.vat || 0; + const discount = item.percentDisc || 0; + const subtotal = + item.quantity * price * (1 + vat / 100) * (1 - discount / 100); + return total + subtotal; + }, 0); + + state.activeInvoice.totalPrice = rawTotal * (1 - action.payload / 100); // Apply the discount + }, + + // Clear the active invoice + clearActiveInvoice(state) { + state.activeInvoice = null; + state.cart = []; // Clear the cart when the invoice is cleared + }, }, -}}); +}); export const { - addProductToCart, - updateCartQuantity, - removeProductFromCart, - setSelectedRow, - updateSelectedRow, - saveDraft, - deleteDraft, - setActiveInvoice, - clearActiveInvoice, - updateInvoiceCurrency, - updateInvoiceDiscount, - updateInvoiceCustomer, - updateInvoiceType, + addProductToCart, + updateCartQuantity, + removeProductFromCart, + setSelectedRow, + updateSelectedRow, + saveDraft, + deleteDraft, + setActiveInvoice, + clearActiveInvoice, + updateInvoiceCurrency, + updateInvoiceDiscount, + updateInvoiceCustomer, + updateInvoiceType, updateInvoicePriceCategory, updateInvoiceInternalNote, updateInvoiceGeneralNote, - } = posSlice.actions; - +} = posSlice.actions; export default posSlice.reducer; - // Selector for total price -export const selectCartTotal = (state: { pos: POSState }) => - state.pos.cart.reduce((total, item) => total + (item.prices[1] || 0) * item.quantity, 0); - +export const selectCartTotal = (state: { pos: POSState }) => + state.pos.cart.reduce( + (total, item) => total + (item.prices[1] || 0) * item.quantity, + 0 + ); -export const selectInvoiceDrafts = (state: { pos: POSState }) => state.pos.invoiceDrafts; +export const selectInvoiceDrafts = (state: { pos: POSState }) => + state.pos.invoiceDrafts; -export const selectActiveInvoice = (state: { pos: POSState }) => state.pos.activeInvoice; +export const selectActiveInvoice = (state: { pos: POSState }) => + state.pos.activeInvoice; // Memoized selector for active invoice cart export const selectActiveInvoiceCart = createSelector( @@ -413,10 +428,9 @@ export const selectActiveInvoiceCart = createSelector( export const selectSelectedCurrency = createSelector( [selectActiveInvoice], - (activeInvoice) => activeInvoice?.selectedCurrency || { id: 1, usdtocurr: 1 } + (activeInvoice) => activeInvoice?.selectedCurrency || { id: 1, usdtocurr: 1 } ); - // Memoized selector for active invoice total export const selectActiveInvoiceTotal = createSelector( @@ -429,9 +443,4 @@ export const selectActiveInvoiceTotal = createSelector( } ); - - - - - -export const { createDraft } = posSlice.actions; \ No newline at end of file +export const { createDraft } = posSlice.actions; From acc075736aa5e70e6e3901c2a35d9f7efb029928 Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Mon, 3 Feb 2025 16:20:06 +0200 Subject: [PATCH 03/23] fixed-bugs --- src/app/[locale]/crm/components/Lead.tsx | 16 +++++++--- src/app/[locale]/crm/components/Leads.tsx | 17 +++++----- .../crm/components/StatusContainer.tsx | 32 ++++++++++++------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index 5008996..bb1d257 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -13,10 +13,16 @@ interface LeadType { interface LeadProps { lead: LeadType; - color: string; } -const Lead: React.FC = ({ lead, color }) => { +const borderColors: Record = { + new: "border-red-500", + qualified: "border-blue-500", + proposition: "border-purple-500", + won: "border-green-500", +}; + +const Lead: React.FC = ({ lead }) => { const { attributes, listeners, setNodeRef, isDragging, transform } = useDraggable({ id: lead.id.toString(), @@ -32,9 +38,9 @@ const Lead: React.FC = ({ lead, color }) => { ref={setNodeRef} {...listeners} {...attributes} - className={`p-4 mb-2 rounded-md bg-slate-50 border-l-8 border-${color} ${ - isDragging ? "opacity-80" : "" - }`} + className={`p-4 mb-2 rounded-md bg-slate-50 border-l-8 border- ${ + borderColors[lead.status] + } ${isDragging ? "opacity-80" : ""}`} >

    {lead.title}

    Price: ${lead.price}

    diff --git a/src/app/[locale]/crm/components/Leads.tsx b/src/app/[locale]/crm/components/Leads.tsx index 8637184..08008a7 100644 --- a/src/app/[locale]/crm/components/Leads.tsx +++ b/src/app/[locale]/crm/components/Leads.tsx @@ -17,10 +17,10 @@ interface LeadsProps { } const statusColors: Record = { - new: "red-500", - qualified: "blue-500", - proposition: "purple-500", - won: "green-500", + new: "bg-red-500", + qualified: "bg-blue-500", + proposition: "bg-purple-500", + won: "bg-green-500", }; const Leads: React.FC = ({ status, leads }) => { @@ -33,12 +33,13 @@ const Leads: React.FC = ({ status, leads }) => { ref={setNodeRef} className={`border rounded-lg shadow-md h-full bg-white p-4`} > -

    - {status.toUpperCase()} -

    +
    +
    +

    {status.toUpperCase()}

    +
      {leads.map((lead) => ( - + ))}
    diff --git a/src/app/[locale]/crm/components/StatusContainer.tsx b/src/app/[locale]/crm/components/StatusContainer.tsx index 9e1db10..f6f6437 100644 --- a/src/app/[locale]/crm/components/StatusContainer.tsx +++ b/src/app/[locale]/crm/components/StatusContainer.tsx @@ -3,6 +3,8 @@ import React, { useState } from "react"; import Leads from "./Leads"; import { DndContext, DragEndEvent } from "@dnd-kit/core"; +import { Button } from "primereact/button"; +import { InputText } from "primereact/inputtext"; interface Lead { id: number; @@ -80,17 +82,25 @@ const StatusContainer: React.FC = () => { }; return ( - -
    - {status.map((s, i) => ( - lead.status === s)} - /> - ))} -
    -
    + <> +
    + + +
    + +
    + {status.map((s, i) => ( + lead.status === s)} + /> + ))} +
    +
    + ); }; From cf7b6f8581c4c4ec68b763222d0d2be13c70adc0 Mon Sep 17 00:00:00 2001 From: mohamadsadem0 Date: Mon, 3 Feb 2025 16:23:11 +0200 Subject: [PATCH 04/23] . --- package-lock.json | 49 ++++++ package.json | 2 + src.zip | Bin 0 -> 104665 bytes src/Components/DragAndDrop.tsx | 165 ++++++++++++++++++ .../brainspace/components/OpportunityCard.tsx | 71 ++++++++ .../[locale]/crm/components/ContactModal.tsx | 59 +++++++ src/app/[locale]/crm/page.tsx | 68 ++++++++ src/data/opportunities.ts | 37 ++++ src/types/dragula.d.ts | 5 + 9 files changed, 456 insertions(+) create mode 100644 src.zip create mode 100644 src/Components/DragAndDrop.tsx create mode 100644 src/app/[locale]/brainspace/components/OpportunityCard.tsx create mode 100644 src/app/[locale]/crm/components/ContactModal.tsx create mode 100644 src/app/[locale]/crm/page.tsx create mode 100644 src/data/opportunities.ts create mode 100644 src/types/dragula.d.ts diff --git a/package-lock.json b/package-lock.json index 2605f42..999be90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "brainspace": "file:", "cookies-next": "^5.0.2", "dayjs": "^1.11.13", + "dragula": "^3.7.3", "file-saver": "^2.0.5", "formik": "^2.4.6", "jspdf": "^2.5.2", @@ -47,6 +48,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@types/dragula": "^3.7.5", "@types/file-saver": "^2.0.7", "@types/node": "^20", "@types/react": "^19", @@ -1635,6 +1637,12 @@ "@tsparticles/engine": "3.7.1" } }, + "node_modules/@types/dragula": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@types/dragula/-/dragula-3.7.5.tgz", + "integrity": "sha512-jojr2JVJB8DawAKXApGnollMvVOMyiMKpchH8gLeoExx35Eq0BQ4WgAiAHoBoEn7h/9eDrIl0yz//cM6ALIJbg==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -2253,6 +2261,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/atoa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atoa/-/atoa-1.0.0.tgz", + "integrity": "sha512-VVE1H6cc4ai+ZXo/CRWoJiHXrA1qfA31DPnx6D20+kSI547hQN5Greh51LQ1baMRMfxO5K5M4ImMtZbZt2DODQ==" + }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -2717,6 +2730,15 @@ "dev": true, "license": "MIT" }, + "node_modules/contra": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/contra/-/contra-1.9.4.tgz", + "integrity": "sha512-N9ArHAqwR/lhPq4OdIAwH4e1btn6EIZMAz4TazjnzCiVECcWUPTma+dRAM38ERImEJBh8NiCCpjoQruSZ+agYg==", + "dependencies": { + "atoa": "1.0.0", + "ticky": "1.0.1" + } + }, "node_modules/cookie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", @@ -2773,6 +2795,14 @@ "node": ">= 8" } }, + "node_modules/crossvent": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/crossvent/-/crossvent-1.5.5.tgz", + "integrity": "sha512-MY4xhBYEnVi+pmTpHCOCsCLYczc0PVtGdPBz6NXNXxikLaUZo4HdAeUb1UqAo3t3yXAloSelTmfxJ+/oUqkW5w==", + "dependencies": { + "custom-event": "^1.0.0" + } + }, "node_modules/css-line-break": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", @@ -2802,6 +2832,11 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -3008,6 +3043,15 @@ "license": "(MPL-2.0 OR Apache-2.0)", "optional": true }, + "node_modules/dragula": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/dragula/-/dragula-3.7.3.tgz", + "integrity": "sha512-/rRg4zRhcpf81TyDhaHLtXt6sEywdfpv1cRUMeFFy7DuypH2U0WUL0GTdyAQvXegviT4PJK4KuMmOaIDpICseQ==", + "dependencies": { + "contra": "1.9.4", + "crossvent": "1.5.5" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -7156,6 +7200,11 @@ "node": ">=0.8" } }, + "node_modules/ticky": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ticky/-/ticky-1.0.1.tgz", + "integrity": "sha512-RX35iq/D+lrsqhcPWIazM9ELkjOe30MSeoBHQHSsRwd1YuhJO5ui1K1/R0r7N3mFvbLBs33idw+eR6j+w6i/DA==" + }, "node_modules/tiny-case": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", diff --git a/package.json b/package.json index 0207951..f5cc148 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "brainspace": "file:", "cookies-next": "^5.0.2", "dayjs": "^1.11.13", + "dragula": "^3.7.3", "file-saver": "^2.0.5", "formik": "^2.4.6", "jspdf": "^2.5.2", @@ -48,6 +49,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@types/dragula": "^3.7.5", "@types/file-saver": "^2.0.7", "@types/node": "^20", "@types/react": "^19", diff --git a/src.zip b/src.zip new file mode 100644 index 0000000000000000000000000000000000000000..746c56d600a04c5b27d570e79262294544af85f0 GIT binary patch literal 104665 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

    T|zDDRZL)^5ndh& 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 = ({ initialSections }) => { + const [sections, setSections] = useState(initialSections); + const containerRefs = useRef([]); + + useEffect(() => { + + const mirrorContainer = document.createElement("div"); + document.body.appendChild(mirrorContainer); + + const drake = dragula(containerRefs.current.filter(Boolean), { + mirrorContainer, + }); + + drake.on("drop", (el: HTMLElement, target: HTMLElement, source: HTMLElement) => { + const sourceId = source.dataset.sectionId; + const targetId = target.dataset.sectionId; + const taskId = el.dataset.taskId; + + if (sourceId && targetId && taskId) { + setSections((prevSections) => { + const updatedSections = [...prevSections]; + + const sourceSection = updatedSections.find((s) => s.id === sourceId); + const taskToMove = sourceSection?.tasks.find((t) => t.id === taskId); + if (sourceSection && taskToMove) { + sourceSection.tasks = sourceSection.tasks.filter((t) => t.id !== taskId); + } + + const targetSection = updatedSections.find((s) => s.id === targetId); + if (targetSection && taskToMove) { + targetSection.tasks.push(taskToMove); + } + + return updatedSections; + }); + } + }); + + return () => { + drake.destroy(); + document.body.removeChild(mirrorContainer); + }; + }, []); + + const addTask = (sectionId: string) => { + const title = prompt("Enter task title:"); + const description = prompt("Enter task description:"); + if (title && description) { + setSections((prevSections) => + prevSections.map((section) => + section.id === sectionId + ? { + ...section, + tasks: [ + ...section.tasks, + { id: `${Date.now()}`, title, description }, + ], + } + : section + ) + ); + } + }; + + const deleteTask = (sectionId: string, taskId: string) => { + setSections((prevSections) => + prevSections.map((section) => + section.id === sectionId + ? { + ...section, + tasks: section.tasks.filter((task) => task.id !== taskId), + } + : section + ) + ); + }; + + return ( +

    + {sections.map((section, index) => ( +
    { + if (el) containerRefs.current[index] = el; + }} + data-section-id={section.id} + style={{ + border: "1px solid #ccc", + padding: "10px", + width: "250px", + minHeight: "300px", + backgroundColor: "#f9f9f9", + borderRadius: "8px", + }} + > +
    +

    {section.name}

    + +
    + {section.tasks.map((task) => ( +
    + {task.title} +

    + {task.description} +

    +
    +))} + +
    + ))} +
    + ); +}; + +export default DragAndDrop; diff --git a/src/app/[locale]/brainspace/components/OpportunityCard.tsx b/src/app/[locale]/brainspace/components/OpportunityCard.tsx new file mode 100644 index 0000000..603602b --- /dev/null +++ b/src/app/[locale]/brainspace/components/OpportunityCard.tsx @@ -0,0 +1,71 @@ +'use client'; + +import React from 'react'; + +const staticOpportunity = { + name: "Zainab Karaki's Opportunity", + expectedRevenue: "0.00 J-J", + probability: 50.0, + contact: "Zainab Karaki", + email: "zainabkaraki@gmail.com", + phone: "N/A", + salesperson: "Zainab Karaki", + expectedClosing: 3, + stage: "Proposition" +}; + +const OpportunityCard: React.FC = () => { + const stages = ["New", "Qualified", "Proposition", "Won"]; + const stageIndex = stages.indexOf(staticOpportunity.stage); + + return ( +
    +
    +
    + Won + Lost +
    +

    {staticOpportunity.name}

    +
    +

    Expected Revenue: {staticOpportunity.expectedRevenue}

    +

    Probability: {staticOpportunity.probability}%

    +

    Contact: {staticOpportunity.contact}

    +

    Email: {staticOpportunity.email}

    +

    Phone: {staticOpportunity.phone}

    +

    Salesperson: {staticOpportunity.salesperson}

    +

    + Expected Closing: {Array.from({ length: 3 }).map((_, i) => ( + + ★ + + ))} +

    +
    +
    +

    Progress:

    +
    + {stages.map((stage, index) => ( + + {stage} + + ))} +
    +
    +
    +
    +
    +
    +

    Internal Notes

    + +
    +
    +
    + ); +}; + +export default OpportunityCard; diff --git a/src/app/[locale]/crm/components/ContactModal.tsx b/src/app/[locale]/crm/components/ContactModal.tsx new file mode 100644 index 0000000..0f45b55 --- /dev/null +++ b/src/app/[locale]/crm/components/ContactModal.tsx @@ -0,0 +1,59 @@ +'use client'; + +import React, { useState } from 'react'; + +const ContactModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => { + const [contactType, setContactType] = useState<'individual' | 'company'>('individual'); + + if (!isOpen) return null; + + return ( +
    +
    +
    +

    Contact

    + +
    + +
    + +
    + +
    + + {contactType === 'company' && } + + + + + + + +
    + +
    + +
    +
    +
    + ); +}; + +export default ContactModal; \ No newline at end of file diff --git a/src/app/[locale]/crm/page.tsx b/src/app/[locale]/crm/page.tsx new file mode 100644 index 0000000..b4dd1de --- /dev/null +++ b/src/app/[locale]/crm/page.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { useSearchParams } from "next/navigation"; +import { use } from "react"; +import DragAndDrop from "@/Components/DragAndDrop"; +import OpportunityCard from "../brainspace/components/OpportunityCard"; + +export default function CRMPage({ params }: { params: Promise<{ locale: string }> }) { + const resolvedParams = use(params); + const { locale } = resolvedParams; + + const searchParams = useSearchParams(); + const token = searchParams.get("token"); + + const sections = [ + { + id: "new", + name: "New", + tasks: [ + { id: "1", title: "Initial Contact", description: "Follow up with the lead" }, + { id: "2", title: "Schedule Meeting", description: "Set up a meeting with the team" }, + ], + }, + { + id: "qualified", + name: "Qualified", + tasks: [ + { id: "3", title: "Review Documents", description: "Check the client documents" }, + ], + }, + { + id: "proposition", + name: "Proposition", + tasks: [], + }, + { + id: "won", + name: "Won", + tasks: [ + { id: "4", title: "Finalize Deal", description: "Send the final agreement to the client" }, + ], + }, + { + id: "stage", + name: "Stage", + tasks: [], + }, + ]; + + if (!token) { + return ( +
    +

    Error: Missing Token

    +

    Please provide a valid token in the URL as a query parameter.

    +

    Example: ?token=your-token-value

    +
    + ); + } + + return ( +
    +

    CRM Page for Locale: {locale}

    +

    Token: {token}

    + + +
    + ); +} diff --git a/src/data/opportunities.ts b/src/data/opportunities.ts new file mode 100644 index 0000000..6997a45 --- /dev/null +++ b/src/data/opportunities.ts @@ -0,0 +1,37 @@ +export interface OpportunityData { + name: string; + expectedRevenue: string; + probability: number; + contact: string; + email: string; + phone?: string; + salesperson: string; + expectedClosing: number; + stage: string; + } + + export const opportunities: OpportunityData[] = [ + { + name: "Zainab Karaki's Opportunity", + expectedRevenue: "0.00 J-J", + probability: 50.0, + contact: "Zainab Karaki", + email: "zainabkaraki@gmail.com", + phone: "", + salesperson: "Zainab Karaki", + expectedClosing: 3, + stage: "Proposition", + }, + { + name: "John Doe's Opportunity", + expectedRevenue: "5,000 USD", + probability: 80.0, + contact: "John Doe", + email: "johndoe@example.com", + phone: "123-456-7890", + salesperson: "Alice Johnson", + expectedClosing: 2, + stage: "Qualified", + }, + ]; + \ No newline at end of file diff --git a/src/types/dragula.d.ts b/src/types/dragula.d.ts new file mode 100644 index 0000000..0a5241d --- /dev/null +++ b/src/types/dragula.d.ts @@ -0,0 +1,5 @@ +declare module 'dragula' { + const dragula: any; + export default dragula; + } + \ No newline at end of file From e03d1cd15ee1bf6854eca5640d03b4ee470aa4ea Mon Sep 17 00:00:00 2001 From: mohamadsadem0 Date: Mon, 3 Feb 2025 21:52:24 +0200 Subject: [PATCH 05/23] . --- src/app/[locale]/crm/components/Lead.tsx | 25 ++++-- .../crm/components/LeadDetailsModal.tsx | 80 ------------------- 2 files changed, 20 insertions(+), 85 deletions(-) delete mode 100644 src/app/[locale]/crm/components/LeadDetailsModal.tsx diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index 9ecd9c3..4528103 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -13,7 +13,7 @@ interface LeadType { interface LeadProps { lead: LeadType; - onLeadClick: (lead: LeadType) => void; + onLeadClick: (lead: LeadType) => void; } const borderColors: Record = { @@ -24,7 +24,13 @@ const borderColors: Record = { }; const Lead: React.FC = ({ lead, onLeadClick }) => { - const { attributes, listeners, setNodeRef, isDragging, transform } = useDraggable({ + const { + attributes, + listeners, + setNodeRef, + isDragging, + transform, + } = useDraggable({ id: lead.id.toString(), }); @@ -34,19 +40,28 @@ const Lead: React.FC = ({ lead, onLeadClick }) => { return (
  • onLeadClick(lead)} + } ${isDragging ? "opacity-80" : ""}`} >

    {lead.title}

    Price: ${lead.price}

    Contact: {lead.name}

    {lead.number}

    + +
  • ); }; diff --git a/src/app/[locale]/crm/components/LeadDetailsModal.tsx b/src/app/[locale]/crm/components/LeadDetailsModal.tsx deleted file mode 100644 index 7ba0967..0000000 --- a/src/app/[locale]/crm/components/LeadDetailsModal.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client'; - -import React from 'react'; -import { Button } from 'primereact/button'; - -export interface Lead { - id: number; - status: string; - title: string; - price: number; - name: string; - number: string; - source?: string; - industry?: string; - assignedTo?: string; - dealValue?: string; - closingDate?: string; - dealStatus?: string; - createdAt?: string; - updatedAt?: string; - note?: string; -} - -interface LeadDetailsModalProps { - lead: Lead; - onClose: () => void; -} - -const LeadDetailsModal: React.FC = ({ lead, onClose }) => { - return ( -
    -
    -
    -

    Lead Details

    - -
    - -
    -

    - Name: {lead.name} -

    -

    - Phone: {lead.number} -

    -

    - Assigned to: {lead.assignedTo} -

    -

    - Deal Value: {lead.dealValue} -

    -

    - Closing Date: {lead.closingDate} -

    -

    - Status: {lead.status} -

    -

    - Note: {lead.note} -

    -
    - -
    - -
    -
    -
    - ); -}; - -export default LeadDetailsModal; From 4ec693e37ae5bd81a85af6a830e401371bac6ad4 Mon Sep 17 00:00:00 2001 From: mohamadsadem0 Date: Mon, 3 Feb 2025 21:54:59 +0200 Subject: [PATCH 06/23] . --- src/app/[locale]/crm/components/Lead.tsx | 56 ++++++++++++++---------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index 4528103..1662238 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -24,16 +24,11 @@ const borderColors: Record = { }; const Lead: React.FC = ({ lead, onLeadClick }) => { - const { - attributes, - listeners, - setNodeRef, - isDragging, - transform, - } = useDraggable({ + const { attributes, listeners, setNodeRef, isDragging, transform } = useDraggable({ id: lead.id.toString(), }); + // Compute style for the Draggable transform const style = { transform: CSS.Translate.toString(transform), }; @@ -42,26 +37,43 @@ const Lead: React.FC = ({ lead, onLeadClick }) => {
  • -

    {lead.title}

    -

    Price: ${lead.price}

    -

    Contact: {lead.name}

    -

    {lead.number}

    - - + + + + + + {/* --- The rest of the lead item, not draggable --- */} +
    +

    {lead.title}

    +

    Price: ${lead.price}

    +

    Contact: {lead.name}

    +

    {lead.number}

    + + +
  • ); }; From d30b4637ac202b179c05106e7e5b577a5f6b6abe Mon Sep 17 00:00:00 2001 From: mohamadsadem0 Date: Mon, 3 Feb 2025 21:56:45 +0200 Subject: [PATCH 07/23] . --- src/app/[locale]/crm/components/Lead.tsx | 56 ++++++++++-------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index 1662238..4528103 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -24,11 +24,16 @@ const borderColors: Record = { }; const Lead: React.FC = ({ lead, onLeadClick }) => { - const { attributes, listeners, setNodeRef, isDragging, transform } = useDraggable({ + const { + attributes, + listeners, + setNodeRef, + isDragging, + transform, + } = useDraggable({ id: lead.id.toString(), }); - // Compute style for the Draggable transform const style = { transform: CSS.Translate.toString(transform), }; @@ -37,43 +42,26 @@ const Lead: React.FC = ({ lead, onLeadClick }) => {
  • - {/* --- The draggable "handle" --- */} -
    - - - -
    - - {/* --- The rest of the lead item, not draggable --- */} -
    -

    {lead.title}

    -

    Price: ${lead.price}

    -

    Contact: {lead.name}

    -

    {lead.number}

    +

    {lead.title}

    +

    Price: ${lead.price}

    +

    Contact: {lead.name}

    +

    {lead.number}

    - -
    +
  • ); }; From e0fa3c2559dd854923d68087031a23265d1e3c63 Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Tue, 4 Feb 2025 13:37:01 +0200 Subject: [PATCH 08/23] fixed-bug --- src/app/[locale]/crm/components/Lead.tsx | 47 +++++++++++------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index 4528103..42e1fae 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -24,37 +24,34 @@ const borderColors: Record = { }; const Lead: React.FC = ({ lead, onLeadClick }) => { - const { - attributes, - listeners, - setNodeRef, - isDragging, - transform, - } = useDraggable({ - id: lead.id.toString(), - }); + const { attributes, listeners, setNodeRef, isDragging, transform } = + useDraggable({ + id: lead.id.toString(), + }); const style = { transform: CSS.Translate.toString(transform), }; return ( -
  • -

    {lead.title}

    -

    Price: ${lead.price}

    -

    Contact: {lead.name}

    -

    {lead.number}

    - +
    +
  • +

    {lead.title}

    +

    Price: ${lead.price}

    +

    Contact: {lead.name}

    +

    {lead.number}

    +
  • - + ); }; From c286559bee25f47f339c2fb3c0c546cedb3eb7a4 Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Tue, 4 Feb 2025 22:42:19 +0200 Subject: [PATCH 09/23] fixed-overlay-problem --- src/app/[locale]/crm/components/Lead.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index 42e1fae..3d7afec 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -34,7 +34,9 @@ const Lead: React.FC = ({ lead, onLeadClick }) => { }; return ( -
    +
  • = ({ lead, onLeadClick }) => { {...attributes} className={`p-4 mb-2 rounded-md bg-slate-50 border-l-8 ${ borderColors[lead.status] - } ${isDragging ? "opacity-80" : ""}`} + }`} >

    {lead.title}

    Price: ${lead.price}

    From d8148959a4cf14ee9521321796af936b2ba1f08e Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Tue, 4 Feb 2025 22:46:18 +0200 Subject: [PATCH 10/23] fixed-bug --- src/app/[locale]/crm/components/OpportunityCard.tsx | 1 - .../[locale]/crm/components/OpportunityDetailsModal.tsx | 7 ------- 2 files changed, 8 deletions(-) diff --git a/src/app/[locale]/crm/components/OpportunityCard.tsx b/src/app/[locale]/crm/components/OpportunityCard.tsx index fd28e27..e14c55b 100644 --- a/src/app/[locale]/crm/components/OpportunityCard.tsx +++ b/src/app/[locale]/crm/components/OpportunityCard.tsx @@ -46,7 +46,6 @@ const OpportunityCard: React.FC = () => { return ( <> - {/* A small card or button to open the modal */}
    setIsOpen(true)} className="border rounded p-4 cursor-pointer hover:shadow-md" diff --git a/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx b/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx index c476381..11c7d59 100644 --- a/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx +++ b/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx @@ -48,7 +48,6 @@ const OpportunityDetailsModal: React.FC = ({ return (
    - {/* Close button (top-right) */} - {/* Header: General Details */}

    General Details

    @@ -124,7 +122,6 @@ const OpportunityDetailsModal: React.FC = ({
    - {/* Tabs */}
    - {/* Tab Content */}
    {activeTab === 'calls' && (
    - {/* The plus button to add a new call */} - {/* Example calls table */} @@ -196,7 +190,6 @@ const OpportunityDetailsModal: React.FC = ({ - {/* Filter row */}
    Outcome ⇵ Note ⇵
    From 683c08d2b0828b98632d6cbcde0984162066a85f Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Wed, 5 Feb 2025 17:19:42 +0200 Subject: [PATCH 11/23] added-leads-drag-sorting --- package-lock.json | 1 + package.json | 1 + src/app/[locale]/crm/components/Lead.tsx | 52 +++++++--- src/app/[locale]/crm/components/Leads.tsx | 42 ++++++-- .../crm/components/StatusContainer.tsx | 97 ++++++++++++++++--- 5 files changed, 154 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index b53581d..d8d904c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@reduxjs/toolkit": "^2.5.0", "@tsparticles/engine": "^3.7.1", "axios": "^1.7.9", diff --git a/package.json b/package.json index b845fb2..8b9c9d7 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@reduxjs/toolkit": "^2.5.0", "@tsparticles/engine": "^3.7.1", "axios": "^1.7.9", diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index 3d7afec..bf57566 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -1,6 +1,7 @@ import React from "react"; import { useDraggable } from "@dnd-kit/core"; import { CSS } from "@dnd-kit/utilities"; +import { useSortable } from "@dnd-kit/sortable"; interface LeadType { id: number; @@ -13,6 +14,7 @@ interface LeadType { interface LeadProps { lead: LeadType; + grabbed: boolean; onLeadClick: (lead: LeadType) => void; } @@ -23,37 +25,57 @@ const borderColors: Record = { won: "border-green-500", }; -const Lead: React.FC = ({ lead, onLeadClick }) => { - const { attributes, listeners, setNodeRef, isDragging, transform } = - useDraggable({ - id: lead.id.toString(), - }); +const Lead: React.FC = ({ lead, onLeadClick, grabbed }) => { + const { + attributes, + listeners, + setNodeRef, + isDragging, + transform, + transition, + } = useSortable({ + id: lead.id, + data: { + type: "Lead", + lead, + }, + }); const style = { + transition, transform: CSS.Translate.toString(transform), }; - return ( -
    -
  • + ); + } + + return ( +
    +

    {lead.title}

    Price: ${lead.price}

    Contact: {lead.name}

    {lead.number}

    -
  • +
    - +
    - {status.map((s) => ( - lead.status === s)} - onLeadClick={handleLeadClick} - /> - ))} + + {status.map((s) => ( + lead.status === s)} + onLeadClick={handleLeadClick} + /> + ))} +
    + {createPortal( + + {activeLead && ( + + )} + , + document.body + )}
    {selectedOpportunity && ( From 360876e59ff59ae7530440127e02eb4308d3144f Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Thu, 6 Feb 2025 18:18:32 +0200 Subject: [PATCH 12/23] finished-drag-sorting --- src/app/[locale]/crm/components/Lead.tsx | 6 +- src/app/[locale]/crm/components/Leads.tsx | 26 ++--- .../crm/components/StatusContainer.tsx | 95 +++++++++++++++---- src/app/[locale]/crm/page.tsx | 6 +- src/app/[locale]/globals.css | 9 +- 5 files changed, 92 insertions(+), 50 deletions(-) diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index bf57566..f8c2a4e 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -51,7 +51,9 @@ const Lead: React.FC = ({ lead, onLeadClick, grabbed }) => {
    ); } @@ -63,7 +65,7 @@ const Lead: React.FC = ({ lead, onLeadClick, grabbed }) => { {...attributes} style={style} className={`p-4 mb-2 rounded-md h-32 bg-slate-100 border-l-8 cursor-grab ${ - grabbed && "cursor-grabbing shadow-xl scale-[1.04] opacity-85" + grabbed && "cursor-grabbing shadow-xl scale-[1.05] opacity-85" } ${borderColors[lead.status]} `} >

    {lead.title}

    diff --git a/src/app/[locale]/crm/components/Leads.tsx b/src/app/[locale]/crm/components/Leads.tsx index 81a619d..84e96d7 100644 --- a/src/app/[locale]/crm/components/Leads.tsx +++ b/src/app/[locale]/crm/components/Leads.tsx @@ -2,7 +2,6 @@ import React, { useMemo } from "react"; import Lead from "./Lead"; import { useDroppable } from "@dnd-kit/core"; import { SortableContext, useSortable } from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; interface LeadType { id: number; @@ -31,31 +30,24 @@ const Leads: React.FC = ({ status, leads, onLeadClick }) => { return leads.map((lead) => lead.id); }, [leads]); - const { setNodeRef, attributes, listeners, transform, transition } = - useSortable({ - id: status, - data: { - type: "status", - status, - }, - }); - - const style = { - transition, - transform: CSS.Translate.toString(transform), - }; + const { setNodeRef } = useSortable({ + id: status, + data: { + type: "Status", + status, + }, + }); return (

    {status.toUpperCase()}

    -
    +
    {leads.map((lead) => ( { - const status: string[] = ["new", "qualified", "proposition", "won"]; + const status: string[] = [ + "new", + "qualified", + "proposition", + "won", + "Asd", + "asdas", + ]; const [leads, setLeads] = useState([ { @@ -115,7 +123,7 @@ const StatusContainer: React.FC = () => { { id: 9, status: "won", - title: "beeflex8", + title: "beeflex9", price: 10000, name: "contact name", number: "+96176123456", @@ -123,7 +131,7 @@ const StatusContainer: React.FC = () => { { id: 10, status: "qualified", - title: "beeflex8", + title: "beeflex10", price: 10000, name: "contact name", number: "+96176123456", @@ -165,30 +173,74 @@ const StatusContainer: React.FC = () => { setIsOpportunityModalOpen(true); }; - const onDragEnd = (event: DragEndEvent) => { - const { active, over } = event; - if (!over) return; + ////this function is needed when using only draggable and not sortable + // const onDragEnd = (event: DragEndEvent) => { + // const { active, over } = event; + // if (!over) return; - const updatedLeads = leads.map((lead) => { - if (lead.id === +active.id) { - return { ...lead, status: over.id as string }; - } - return lead; - }); - setLeads(updatedLeads); - }; + // const updatedLeads = leads.map((lead) => { + // if (lead.id === +active.id) { + // return { ...lead, status: over.id as string }; + // } + // return lead; + // }); + // setLeads(updatedLeads); + // }; const onDragStart = function (event: DragStartEvent) { - console.log(event.active.data.current?.lead); if (event.active.data.current?.type === "Lead") { setActiveLead(event.active.data.current.lead); return; } }; + const onDragOver = function (event: DragOverEvent) { + const { active, over } = event; + if (!over) return; + const activeId = active.id; + const overId = over.id; + + if (activeId === overId) return; + + const isActiveLead = active.data.current?.type === "Lead"; + const isOverLead = over.data.current?.type === "Lead"; + + if (!isActiveLead) return; + + //dropping a lead over another lead + if (isActiveLead && isOverLead) { + setLeads((leads) => { + const activeIndex = leads.findIndex((lead) => lead.id === activeId); + const overIndex = leads.findIndex((lead) => lead.id === overId); + + if (leads[activeIndex].status !== leads[overIndex].status) { + leads[activeIndex].status = leads[overIndex].status; + } + + return arrayMove(leads, activeIndex, overIndex); + }); + } + + const isOverStatus = over.data.current?.type === "Status"; + + //dropping a lead over a status column + + if (isActiveLead && isOverStatus) { + setLeads((leads) => { + const activeIndex = leads.findIndex((lead) => lead.id === activeId); + + if (leads[activeIndex].status !== overId.toString()) { + leads[activeIndex].status = overId.toString(); + } + + return arrayMove(leads, activeIndex, activeIndex); + }); + } + }; + return ( - <> -
    +
    +
    - -
    + + +
    {status.map((s) => ( { onClose={() => setIsContactModalOpen(false)} /> )} - +
    ); }; diff --git a/src/app/[locale]/crm/page.tsx b/src/app/[locale]/crm/page.tsx index 10dd641..e8f1e6d 100644 --- a/src/app/[locale]/crm/page.tsx +++ b/src/app/[locale]/crm/page.tsx @@ -2,9 +2,7 @@ import React from "react"; import StatusContainer from "./components/StatusContainer"; const Page = () => { - return <> - - ; + return ; }; -export default Page; \ No newline at end of file +export default Page; diff --git a/src/app/[locale]/globals.css b/src/app/[locale]/globals.css index 896e8f0..d9299a1 100644 --- a/src/app/[locale]/globals.css +++ b/src/app/[locale]/globals.css @@ -12,7 +12,6 @@ body { background-color: var(--bg-color); } - /* Custom Pagination Styles */ .custom-swiper-pagination .swiper-pagination-bullet { width: var(--swiper-pagination-bullet-size) !important; @@ -121,7 +120,6 @@ body { /* Ensure the input doesn't overflow */ } - /* Ensure higher specificity */ .custom-select-button .p-button, /* For individual buttons */ .custom-select-button .p-highlight { @@ -132,14 +130,15 @@ body { /* Handle hover state */ .custom-select-button .p-button:hover { - background-color: var(--main-color) !important; /* Maintain main color on hover */ + background-color: var( + --main-color + ) !important; /* Maintain main color on hover */ color: white !important; } /* Transition for smooth effect */ .custom-select-button .p-button { transition: background-color 0.3s, color 0.3s; - } .custom-tooltip { @@ -150,5 +149,3 @@ body { border-radius: 4px; /* Optional: Add rounded corners */ padding: 5px 10px; /* Optional: Adjust padding */ } - - From 562260b200b51ac1d586cb38401adcb11248f7b9 Mon Sep 17 00:00:00 2001 From: mohamadsadem0 Date: Fri, 7 Feb 2025 14:57:53 +0200 Subject: [PATCH 13/23] . --- .../[locale]/crm/components/AddCallModal.tsx | 109 ++++++ .../components/OpportunityDetailsModal.tsx | 328 ++++++------------ 2 files changed, 213 insertions(+), 224 deletions(-) create mode 100644 src/app/[locale]/crm/components/AddCallModal.tsx diff --git a/src/app/[locale]/crm/components/AddCallModal.tsx b/src/app/[locale]/crm/components/AddCallModal.tsx new file mode 100644 index 0000000..3d382e1 --- /dev/null +++ b/src/app/[locale]/crm/components/AddCallModal.tsx @@ -0,0 +1,109 @@ +'use client'; + +import React, { useState } from 'react'; +import { Dialog } from 'primereact/dialog'; +import { Dropdown } from 'primereact/dropdown'; +import { InputText } from 'primereact/inputtext'; +import { Calendar } from 'primereact/calendar'; +import { Button } from 'primereact/button'; +import { InputTextarea } from 'primereact/inputtextarea'; + +const AddCallModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => { + const [salesRep, setSalesRep] = useState(null); + const [callDate, setCallDate] = useState(null); + const [duration, setDuration] = useState(''); + const [outcome] = useState('01/02/2025'); // Default outcome + const [note, setNote] = useState(''); + + const salesRepOptions = [ + { label: 'Employee 1', value: 'employee1' }, + { label: 'Employee 2', value: 'employee2' }, + { label: 'Employee 3', value: 'employee3' }, + ]; + + return ( + + {/* Header */} +
    +

    Add Call

    +
    + + {/* Content */} +
    +
    + {/* Sales Rep ID */} +
    + + setSalesRep(e.value)} + options={salesRepOptions} + placeholder="Employee name" + className="w-full p-inputtext border-gray-300 rounded-md" + /> +
    + + {/* Call Date */} +
    + + setCallDate(e.value as Date)} + placeholder="Enter call date" + className="w-full p-inputtext border-gray-300 rounded-md" + /> +
    + + {/* Call Duration */} +
    + + setDuration(e.target.value)} + placeholder="Enter call duration" + className="w-full border-gray-300 rounded-md" + /> +
    + + {/* Outcome (Pre-filled) */} +
    + + +
    + + {/* Note */} +
    + + setNote(e.target.value)} + placeholder="Add note" + rows={4} + className="w-full border-gray-300 rounded-md" + /> +
    +
    +
    + + {/* Footer - Create Button */} +
    +
    +
    + ); +}; + +export default AddCallModal; diff --git a/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx b/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx index 11c7d59..fd6c35d 100644 --- a/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx +++ b/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx @@ -1,6 +1,17 @@ 'use client'; import React, { useState } from 'react'; +import { Dialog } from 'primereact/dialog'; +import { TabView, TabPanel } from 'primereact/tabview'; +import { DataTable } from 'primereact/datatable'; +import { Column } from 'primereact/column'; +import { Button } from 'primereact/button'; +import { Tag } from 'primereact/tag'; +import AddCallModal from './AddCallModal'; // Ensure the correct path + +import 'primereact/resources/themes/saga-blue/theme.css'; +import 'primereact/resources/primereact.min.css'; +import 'primeicons/primeicons.css'; interface Opportunity { id: number; @@ -21,7 +32,6 @@ interface Opportunity { note: string; } - interface OpportunityDetailsModalProps { isOpen: boolean; onClose: () => void; @@ -33,241 +43,111 @@ const OpportunityDetailsModal: React.FC = ({ onClose, data, }) => { - const [activeTab, setActiveTab] = useState<'calls' | 'follow-up' | 'meetings' | 'notes'>('calls'); - - if (!isOpen) return null; + const [activeIndex, setActiveIndex] = useState(0); + const [isAddCallModalOpen, setIsAddCallModalOpen] = useState(false); const mockCalls = [ - { id: 1, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '1 Hour', outcome: '00000', note: 'Lorem ipsum...' }, - { id: 2, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '30 Min', outcome: '00000', note: 'Lorem ipsum...' }, - { id: 3, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '45 Min', outcome: '00000', note: 'Lorem ipsum...' }, - { id: 4, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '20 Min', outcome: '00000', note: 'Lorem ipsum...' }, - { id: 5, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '1 Hour, 10 Min', outcome: '00000', note: 'Lorem ipsum...' }, + { id: 1, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '1 Hour', outcome: '00000', note: 'Lorem ipsum dolor sit amet consectetur...' }, + { id: 2, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '30 Min', outcome: '00000', note: 'Lorem ipsum dolor sit amet consectetur...' }, + { id: 3, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '45 Min', outcome: '00000', note: 'Lorem ipsum dolor sit amet consectetur...' }, + { id: 4, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '20 Min', outcome: '00000', note: 'Lorem ipsum dolor sit amet consectetur...' }, + { id: 5, salesRep: 'Name Here', callDate: 'Jan 20,2025', duration: '1 Hour, 10 Min', outcome: '00000', note: 'Lorem ipsum dolor sit amet consectetur...' }, ]; return ( -
    -
    - - -

    General Details

    - -
    -
    -

    - ID: {data.id} -

    -

    - Lead ID: {data.leadId} -

    -

    - Name: {data.name} -

    -

    - Phone: {data.phone} -

    -

    - Email: {data.email} -

    -

    - Note:
    - {data.note} -

    -
    -
    -

    - Source: {data.source} -

    -

    - Industry: {data.industry} -

    -

    - Assigned to: {data.assignedTo} -

    -

    - Status:{' '} - - {data.status} - -

    -

    - Next Follow up: {data.nextFollowUp} -

    -
    -
    -

    - Deal Value: {data.dealValue} -

    -

    - Closing Date: {data.closingDate} -

    -

    - Deal Status:{' '} - - {data.dealStatus} - -

    -

    - Created at: {data.createdAt} -

    -

    - Updated at: {data.updatedAt} -

    -
    + <> + + {/* Header */} +
    + General Details +
    -
    -
    - - - - -
    - -
    - {activeTab === 'calls' && ( -
    - - - - - - - - - - - - - - - - - - - - - - - - - {mockCalls.map((call) => ( - - - - - - - - - - ))} - -
    - - ID ⇵Sales rep ⇵Call Date ⇵Call Duration ⇵Outcome ⇵Note ⇵
    - - - - - - - - - - - -
    - - {call.id}{call.salesRep}{call.callDate}{call.duration}{call.outcome}{call.note}
    -
    - )} - - {activeTab === 'follow-up' && ( -
    -

    Follow-up content here...

    + {/* Main Content */} +
    + {/* Details Section */} +
    +
    +
    +

    ID: {data.id}

    +

    Lead ID: {data.leadId}

    +

    Name: {data.name}

    +

    Phone: {data.phone}

    +

    Email: {data.email}

    +

    Note:

    +
    {data.note}
    - )} - {activeTab === 'meetings' && ( -
    -

    Meetings content here...

    +
    +

    Source: {data.source}

    +

    Industry: {data.industry}

    +

    Assigned to: {data.assignedTo}

    +

    + Status: +

    +

    Next Follow-up: {data.nextFollowUp}

    - )} - {activeTab === 'notes' && ( -
    -

    Notes content here...

    +
    +

    Deal Value: {data.dealValue}

    +

    Closing Date: {data.closingDate}

    +

    + Deal Status: +

    +

    Created at: {data.createdAt}

    +

    Updated at: {data.updatedAt}

    - )} +
    + + {/* Tabs Section */} + setActiveIndex(e.index)} className="mt-6"> + +
    +
    +
    + + +
    Follow-up content here...
    +
    + + +
    Meetings content here...
    +
    + + +
    Notes content here...
    +
    +
    -
    -
    +
    + + {/* AddCallModal rendered on top */} + setIsAddCallModalOpen(false)} + /> + ); }; From ba8aedb9c144da60347f5e6687db6a6d14b2befe Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Fri, 7 Feb 2025 15:11:51 +0200 Subject: [PATCH 14/23] added-prime-react-theme --- .../crm/components/StatusContainer.tsx | 35 ++++++++----------- src/app/[locale]/layout.tsx | 10 +++--- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/app/[locale]/crm/components/StatusContainer.tsx b/src/app/[locale]/crm/components/StatusContainer.tsx index 1b21b2d..22ac69f 100644 --- a/src/app/[locale]/crm/components/StatusContainer.tsx +++ b/src/app/[locale]/crm/components/StatusContainer.tsx @@ -173,20 +173,6 @@ const StatusContainer: React.FC = () => { setIsOpportunityModalOpen(true); }; - ////this function is needed when using only draggable and not sortable - // const onDragEnd = (event: DragEndEvent) => { - // const { active, over } = event; - // if (!over) return; - - // const updatedLeads = leads.map((lead) => { - // if (lead.id === +active.id) { - // return { ...lead, status: over.id as string }; - // } - // return lead; - // }); - // setLeads(updatedLeads); - // }; - const onDragStart = function (event: DragStartEvent) { if (event.active.data.current?.type === "Lead") { setActiveLead(event.active.data.current.lead); @@ -241,13 +227,20 @@ const StatusContainer: React.FC = () => { return (
    - - + +
    +
    +
    diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 0da2fed..52943e0 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -4,7 +4,9 @@ import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import ConditionalLayout from "./ConditionalLayout"; import ReduxProvider from "@/redux/ReduxProvider"; - +import "primereact/resources/themes/lara-light-blue/theme.css"; +import "primereact/resources/primereact.min.css"; +import "primeicons/primeicons.css"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -51,11 +53,7 @@ export default async function RootLayout({ > - - - {children} - - + {children} From 9ea5a0c5f49cf6b665a7955a1d07942a2241c206 Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Fri, 7 Feb 2025 23:51:23 +0200 Subject: [PATCH 15/23] styled-header --- src/app/[locale]/crm/components/Leads.tsx | 34 ++++++++++--------- .../crm/components/StatusContainer.tsx | 29 +++++++++------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/app/[locale]/crm/components/Leads.tsx b/src/app/[locale]/crm/components/Leads.tsx index 84e96d7..18a59d6 100644 --- a/src/app/[locale]/crm/components/Leads.tsx +++ b/src/app/[locale]/crm/components/Leads.tsx @@ -39,25 +39,27 @@ const Leads: React.FC = ({ status, leads, onLeadClick }) => { }); return ( -
    -
    +
    +

    {status.toUpperCase()}

    -
    - - {leads.map((lead) => ( - - ))} - +
    +
    + + {leads.map((lead) => ( + + ))} + +
    ); diff --git a/src/app/[locale]/crm/components/StatusContainer.tsx b/src/app/[locale]/crm/components/StatusContainer.tsx index 22ac69f..e701fa0 100644 --- a/src/app/[locale]/crm/components/StatusContainer.tsx +++ b/src/app/[locale]/crm/components/StatusContainer.tsx @@ -2,10 +2,11 @@ import React, { useState } from "react"; import Leads from "./Leads"; -import { Button } from "primereact/button"; -import { InputText } from "primereact/inputtext"; import OpportunityDetailsModal from "./OpportunityDetailsModal"; import ContactModal from "./ContactModal"; +import { Button } from "primereact/button"; +import { ButtonGroup } from "primereact/buttongroup"; +import { InputText } from "primereact/inputtext"; import { DndContext, DragEndEvent, @@ -225,26 +226,28 @@ const StatusContainer: React.FC = () => { }; return ( -
    -
    - -
    +
    +
    + +
    + +
    -
    +
    {status.map((s) => ( Date: Mon, 10 Feb 2025 12:58:35 +0200 Subject: [PATCH 16/23] changed-statusCantainer-name-to-LeadsManager --- src/app/[locale]/crm/components/Lead.tsx | 12 +++++------- .../{StatusContainer.tsx => LeadsManager.tsx} | 5 ++--- src/app/[locale]/crm/page.tsx | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) rename src/app/[locale]/crm/components/{StatusContainer.tsx => LeadsManager.tsx} (98%) diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/Lead.tsx index f8c2a4e..4b79dab 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/Lead.tsx @@ -2,6 +2,7 @@ import React from "react"; import { useDraggable } from "@dnd-kit/core"; import { CSS } from "@dnd-kit/utilities"; import { useSortable } from "@dnd-kit/sortable"; +import { Button } from "primereact/button"; interface LeadType { id: number; @@ -73,18 +74,15 @@ const Lead: React.FC = ({ lead, onLeadClick, grabbed }) => {

    Contact: {lead.name}

    {lead.number}

    - + />
    ); }; diff --git a/src/app/[locale]/crm/components/StatusContainer.tsx b/src/app/[locale]/crm/components/LeadsManager.tsx similarity index 98% rename from src/app/[locale]/crm/components/StatusContainer.tsx rename to src/app/[locale]/crm/components/LeadsManager.tsx index e701fa0..ddd07c8 100644 --- a/src/app/[locale]/crm/components/StatusContainer.tsx +++ b/src/app/[locale]/crm/components/LeadsManager.tsx @@ -9,7 +9,6 @@ import { ButtonGroup } from "primereact/buttongroup"; import { InputText } from "primereact/inputtext"; import { DndContext, - DragEndEvent, DragOverEvent, DragOverlay, DragStartEvent, @@ -46,7 +45,7 @@ interface Opportunity { note: string; } -const StatusContainer: React.FC = () => { +const LeadsManager: React.FC = () => { const status: string[] = [ "new", "qualified", @@ -291,4 +290,4 @@ const StatusContainer: React.FC = () => { ); }; -export default StatusContainer; +export default LeadsManager; diff --git a/src/app/[locale]/crm/page.tsx b/src/app/[locale]/crm/page.tsx index e8f1e6d..000c070 100644 --- a/src/app/[locale]/crm/page.tsx +++ b/src/app/[locale]/crm/page.tsx @@ -1,8 +1,8 @@ import React from "react"; -import StatusContainer from "./components/StatusContainer"; +import LeadsManager from "./components/LeadsManager"; const Page = () => { - return ; + return ; }; export default Page; From b8a6ce36915b9ccc9b41c7edf362931c7020faff Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Mon, 10 Feb 2025 14:16:31 +0200 Subject: [PATCH 17/23] added-redux --- .../crm/components/{Lead.tsx => LeadCard.tsx} | 4 +- src/app/[locale]/crm/components/Leads.tsx | 5 +- .../[locale]/crm/components/LeadsManager.tsx | 230 +++--------------- src/redux/slices/leadsSlice.ts | 148 +++++++++++ src/redux/store.ts | 16 +- 5 files changed, 200 insertions(+), 203 deletions(-) rename src/app/[locale]/crm/components/{Lead.tsx => LeadCard.tsx} (94%) create mode 100644 src/redux/slices/leadsSlice.ts diff --git a/src/app/[locale]/crm/components/Lead.tsx b/src/app/[locale]/crm/components/LeadCard.tsx similarity index 94% rename from src/app/[locale]/crm/components/Lead.tsx rename to src/app/[locale]/crm/components/LeadCard.tsx index 4b79dab..ab047ec 100644 --- a/src/app/[locale]/crm/components/Lead.tsx +++ b/src/app/[locale]/crm/components/LeadCard.tsx @@ -26,7 +26,7 @@ const borderColors: Record = { won: "border-green-500", }; -const Lead: React.FC = ({ lead, onLeadClick, grabbed }) => { +const LeadCard: React.FC = ({ lead, onLeadClick, grabbed }) => { const { attributes, listeners, @@ -87,4 +87,4 @@ const Lead: React.FC = ({ lead, onLeadClick, grabbed }) => { ); }; -export default Lead; +export default LeadCard; diff --git a/src/app/[locale]/crm/components/Leads.tsx b/src/app/[locale]/crm/components/Leads.tsx index 18a59d6..2fffa89 100644 --- a/src/app/[locale]/crm/components/Leads.tsx +++ b/src/app/[locale]/crm/components/Leads.tsx @@ -1,6 +1,5 @@ import React, { useMemo } from "react"; -import Lead from "./Lead"; -import { useDroppable } from "@dnd-kit/core"; +import LeadCard from "./LeadCard"; import { SortableContext, useSortable } from "@dnd-kit/sortable"; interface LeadType { @@ -51,7 +50,7 @@ const Leads: React.FC = ({ status, leads, onLeadClick }) => {
    {leads.map((lead) => ( - { - const status: string[] = [ - "new", - "qualified", - "proposition", - "won", - "Asd", - "asdas", - ]; + const dispatch = useDispatch(); + const leads = useSelector((state: RootState) => state.leads.leads); + const status = ["new", "qualified", "proposition", "won"]; - const [leads, setLeads] = useState([ - { - id: 1, - status: "new", - title: "beeflex1", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 2, - status: "new", - title: "beeflex2", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 3, - status: "new", - title: "beeflex3", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 4, - status: "new", - title: "beeflex4", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 5, - status: "qualified", - title: "beeflex5", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 6, - status: "qualified", - title: "beeflex6", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 7, - status: "proposition", - title: "beeflex7", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 8, - status: "won", - title: "beeflex8", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 9, - status: "won", - title: "beeflex9", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - { - id: 10, - status: "qualified", - title: "beeflex10", - price: 10000, - name: "contact name", - number: "+96176123456", - }, - ]); - - const [selectedOpportunity, setSelectedOpportunity] = - useState(null); + const [selectedOpportunity, setSelectedOpportunity] = useState( + null + ); const [isOpportunityModalOpen, setIsOpportunityModalOpen] = useState(false); - const [isContactModalOpen, setIsContactModalOpen] = useState(false); - const [activeLead, setActiveLead] = useState(null); - const mapLeadToOpportunity = (lead: Lead): Opportunity => { - return { - id: lead.id, - leadId: lead.id, - name: lead.name, - phone: lead.number, - email: "", - source: "", - industry: "", - assignedTo: "", - status: lead.status, - nextFollowUp: "", - dealValue: lead.price.toString(), - closingDate: "", - dealStatus: "Open", - createdAt: "", - updatedAt: "", - note: lead.title ?? "", - }; - }; - const handleLeadClick = (lead: Lead) => { - const converted = mapLeadToOpportunity(lead); - setSelectedOpportunity(converted); + setSelectedOpportunity(lead); setIsOpportunityModalOpen(true); }; - const onDragStart = function (event: DragStartEvent) { + const onDragStart = (event: DragStartEvent) => { if (event.active.data.current?.type === "Lead") { - setActiveLead(event.active.data.current.lead); - return; + setActiveLead(event.active.data.current.lead as Lead); } }; - const onDragOver = function (event: DragOverEvent) { + const onDragOver = (event: DragOverEvent) => { const { active, over } = event; if (!over) return; + const activeId = active.id; const overId = over.id; @@ -193,34 +60,18 @@ const LeadsManager: React.FC = () => { if (!isActiveLead) return; - //dropping a lead over another lead + // Dropping a lead over another lead if (isActiveLead && isOverLead) { - setLeads((leads) => { - const activeIndex = leads.findIndex((lead) => lead.id === activeId); - const overIndex = leads.findIndex((lead) => lead.id === overId); + const activeIndex = leads.findIndex((lead) => lead.id === activeId); + const overIndex = leads.findIndex((lead) => lead.id === overId); - if (leads[activeIndex].status !== leads[overIndex].status) { - leads[activeIndex].status = leads[overIndex].status; - } + if (leads[activeIndex].status !== leads[overIndex].status) { + dispatch( + updateLeadStatus({ id: +activeId, status: leads[overIndex].status }) + ); + } - return arrayMove(leads, activeIndex, overIndex); - }); - } - - const isOverStatus = over.data.current?.type === "Status"; - - //dropping a lead over a status column - - if (isActiveLead && isOverStatus) { - setLeads((leads) => { - const activeIndex = leads.findIndex((lead) => lead.id === activeId); - - if (leads[activeIndex].status !== overId.toString()) { - leads[activeIndex].status = overId.toString(); - } - - return arrayMove(leads, activeIndex, activeIndex); - }); + dispatch(reorderLeads({ fromIndex: activeIndex, toIndex: overIndex })); } }; @@ -232,7 +83,7 @@ const LeadsManager: React.FC = () => {
    {/* Main Content */} @@ -76,37 +115,71 @@ const OpportunityDetailsModal: React.FC = ({
    -

    ID: {data.id}

    -

    Lead ID: {data.leadId}

    -

    Name: {data.name}

    -

    Phone: {data.phone}

    -

    Email: {data.email}

    -

    Note:

    +

    + ID: {data.id} +

    +

    + Lead ID: {data.leadId} +

    +

    + Name: {data.name} +

    +

    + Phone: {data.phone} +

    +

    + Email: {data.email} +

    +

    + Note: +

    {data.note}
    -

    Source: {data.source}

    -

    Industry: {data.industry}

    -

    Assigned to: {data.assignedTo}

    - Status: + Source: {data.source} +

    +

    + Industry: {data.industry} +

    +

    + Assigned to: {data.assignedTo} +

    +

    + Status:{" "} + +

    +

    + Next Follow-up: {data.nextFollowUp}

    -

    Next Follow-up: {data.nextFollowUp}

    -

    Deal Value: {data.dealValue}

    -

    Closing Date: {data.closingDate}

    - Deal Status: + Deal Value: {data.dealValue} +

    +

    + Closing Date: {data.closingDate} +

    +

    + Deal Status:{" "} + +

    +

    + Created at: {data.createdAt} +

    +

    + Updated at: {data.updatedAt}

    -

    Created at: {data.createdAt}

    -

    Updated at: {data.updatedAt}

    {/* Tabs Section */} - setActiveIndex(e.index)} className="mt-6"> + setActiveIndex(e.index)} + className="mt-6" + >
    -
    Follow-up content here...
    +
    + Follow-up content here... +
    -
    Meetings content here...
    +
    + Meetings content here... +
    -
    Notes content here...
    +
    + Notes content here... +
    diff --git a/src/redux/slices/leadsSlice.ts b/src/redux/slices/leadsSlice.ts index 057c3ad..679bd7e 100644 --- a/src/redux/slices/leadsSlice.ts +++ b/src/redux/slices/leadsSlice.ts @@ -2,15 +2,16 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { arrayMove } from "@dnd-kit/sortable"; export interface Lead { - id: number; - status: string; + number: string; title: string; price: number; + id: number; + status: string; name: string; - number: string; } export interface Opportunity { + lead: Lead; id: number; leadId: number; name: string; From cceb8058b04ea2ab109cb5a8e1caee8ab91d51ca Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Mon, 10 Feb 2025 14:54:51 +0200 Subject: [PATCH 19/23] fixed-datatypes --- src/app/[locale]/crm/components/LeadCard.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/app/[locale]/crm/components/LeadCard.tsx b/src/app/[locale]/crm/components/LeadCard.tsx index ab047ec..226c517 100644 --- a/src/app/[locale]/crm/components/LeadCard.tsx +++ b/src/app/[locale]/crm/components/LeadCard.tsx @@ -3,15 +3,7 @@ import { useDraggable } from "@dnd-kit/core"; import { CSS } from "@dnd-kit/utilities"; import { useSortable } from "@dnd-kit/sortable"; import { Button } from "primereact/button"; - -interface LeadType { - id: number; - status: string; - title: string; - price: number; - name: string; - number: string; -} +import { Lead as LeadType } from "@/redux/slices/leadsSlice"; interface LeadProps { lead: LeadType; From f64b0715a093f28d45333bf90d14cb61071ce20e Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Mon, 10 Feb 2025 15:24:54 +0200 Subject: [PATCH 20/23] refactored-code --- src/app/[locale]/crm/components/Header.tsx | 36 ++++++++++ src/app/[locale]/crm/components/LeadCard.tsx | 25 +++++-- src/app/[locale]/crm/components/Leads.tsx | 10 +-- .../[locale]/crm/components/LeadsManager.tsx | 70 ++++--------------- 4 files changed, 72 insertions(+), 69 deletions(-) create mode 100644 src/app/[locale]/crm/components/Header.tsx diff --git a/src/app/[locale]/crm/components/Header.tsx b/src/app/[locale]/crm/components/Header.tsx new file mode 100644 index 0000000..c692dc1 --- /dev/null +++ b/src/app/[locale]/crm/components/Header.tsx @@ -0,0 +1,36 @@ +import { Button } from "primereact/button"; +import { ButtonGroup } from "primereact/buttongroup"; +import { InputText } from "primereact/inputtext"; +import React, { useState } from "react"; +import ContactModal from "./ContactModal"; + +export default function Header() { + const [isContactModalOpen, setIsContactModalOpen] = useState(false); + + return ( +
    + +
    + +
    + {isContactModalOpen && ( + setIsContactModalOpen(false)} + /> + )} +
    + ); +} diff --git a/src/app/[locale]/crm/components/LeadCard.tsx b/src/app/[locale]/crm/components/LeadCard.tsx index 226c517..2f121d4 100644 --- a/src/app/[locale]/crm/components/LeadCard.tsx +++ b/src/app/[locale]/crm/components/LeadCard.tsx @@ -1,14 +1,14 @@ -import React from "react"; +import React, { useState } from "react"; import { useDraggable } from "@dnd-kit/core"; import { CSS } from "@dnd-kit/utilities"; import { useSortable } from "@dnd-kit/sortable"; import { Button } from "primereact/button"; import { Lead as LeadType } from "@/redux/slices/leadsSlice"; +import OpportunityDetailsModal from "./OpportunityDetailsModal"; interface LeadProps { lead: LeadType; grabbed: boolean; - onLeadClick: (lead: LeadType) => void; } const borderColors: Record = { @@ -18,7 +18,12 @@ const borderColors: Record = { won: "border-green-500", }; -const LeadCard: React.FC = ({ lead, onLeadClick, grabbed }) => { +const LeadCard: React.FC = ({ lead, grabbed }) => { + const [selectedOpportunity, setSelectedOpportunity] = useState( + null + ); + const [isOpportunityModalOpen, setIsOpportunityModalOpen] = useState(false); + const { attributes, listeners, @@ -39,6 +44,11 @@ const LeadCard: React.FC = ({ lead, onLeadClick, grabbed }) => { transform: CSS.Translate.toString(transform), }; + const handleLeadClick = (lead: LeadType) => { + setSelectedOpportunity(lead); + setIsOpportunityModalOpen(true); + }; + if (isDragging) { return (
    = ({ lead, onLeadClick, grabbed }) => { className={`absolute top-2 right-2 ${grabbed && "scale-[1.03]"}`} onClick={(e) => { e.stopPropagation(); - onLeadClick(lead); + handleLeadClick(lead); }} /> + {selectedOpportunity && ( + setIsOpportunityModalOpen(false)} + data={selectedOpportunity} + /> + )}
    ); }; diff --git a/src/app/[locale]/crm/components/Leads.tsx b/src/app/[locale]/crm/components/Leads.tsx index 04dc231..401dd91 100644 --- a/src/app/[locale]/crm/components/Leads.tsx +++ b/src/app/[locale]/crm/components/Leads.tsx @@ -6,7 +6,6 @@ import { Lead as LeadType } from "@/redux/slices/leadsSlice"; interface LeadsProps { status: string; leads: LeadType[]; - onLeadClick: (lead: LeadType) => void; } const statusColors: Record = { @@ -16,7 +15,7 @@ const statusColors: Record = { won: "bg-green-500", }; -const Leads: React.FC = ({ status, leads, onLeadClick }) => { +const Leads: React.FC = ({ status, leads }) => { const leadsIds = useMemo(() => { return leads.map((lead) => lead.id); }, [leads]); @@ -42,12 +41,7 @@ const Leads: React.FC = ({ status, leads, onLeadClick }) => {
    {leads.map((lead) => ( - + ))}
    diff --git a/src/app/[locale]/crm/components/LeadsManager.tsx b/src/app/[locale]/crm/components/LeadsManager.tsx index 297b6c7..ac831e6 100644 --- a/src/app/[locale]/crm/components/LeadsManager.tsx +++ b/src/app/[locale]/crm/components/LeadsManager.tsx @@ -5,11 +5,6 @@ import { useSelector, useDispatch } from "react-redux"; import { RootState } from "@/redux/store"; import { updateLeadStatus, reorderLeads } from "@/redux/slices/leadsSlice"; import Leads from "./Leads"; -import OpportunityDetailsModal from "./OpportunityDetailsModal"; -import ContactModal from "./ContactModal"; -import { Button } from "primereact/button"; -import { ButtonGroup } from "primereact/buttongroup"; -import { InputText } from "primereact/inputtext"; import { DndContext, DragOverEvent, @@ -18,25 +13,16 @@ import { } from "@dnd-kit/core"; import { createPortal } from "react-dom"; import LeadCard from "./LeadCard"; -import { Lead, Opportunity } from "@/redux/slices/leadsSlice"; +import { Lead } from "@/redux/slices/leadsSlice"; +import Header from "./Header"; const LeadsManager: React.FC = () => { const dispatch = useDispatch(); const leads = useSelector((state: RootState) => state.leads.leads); const status = ["new", "qualified", "proposition", "won"]; - const [selectedOpportunity, setSelectedOpportunity] = useState( - null - ); - const [isOpportunityModalOpen, setIsOpportunityModalOpen] = useState(false); - const [isContactModalOpen, setIsContactModalOpen] = useState(false); const [activeLead, setActiveLead] = useState(null); - const handleLeadClick = (lead: Lead) => { - setSelectedOpportunity(lead); - setIsOpportunityModalOpen(true); - }; - const onDragStart = (event: DragStartEvent) => { if (event.active.data.current?.type === "Lead") { setActiveLead(event.active.data.current.lead as Lead); @@ -54,14 +40,15 @@ const LeadsManager: React.FC = () => { const isActiveLead = active.data.current?.type === "Lead"; const isOverLead = over.data.current?.type === "Lead"; + const isOverStatus = over.data.current?.type === "Status"; if (!isActiveLead) return; - // Dropping a lead over another lead if (isActiveLead && isOverLead) { const activeIndex = leads.findIndex((lead) => lead.id === activeId); const overIndex = leads.findIndex((lead) => lead.id === overId); + ////dropping the lead over in the same column if (leads[activeIndex].status !== leads[overIndex].status) { dispatch( updateLeadStatus({ id: +activeId, status: leads[overIndex].status }) @@ -70,28 +57,18 @@ const LeadsManager: React.FC = () => { dispatch(reorderLeads({ fromIndex: activeIndex, toIndex: overIndex })); } + + ////dropping the lead over a different column + if (isActiveLead && isOverStatus) { + const activeIndex = leads.findIndex((lead) => lead.id === activeId); + + dispatch(updateLeadStatus({ id: +activeId, status: overId.toString() })); + } }; return (
    -
    - -
    - -
    -
    +
    @@ -100,37 +77,16 @@ const LeadsManager: React.FC = () => { key={s} status={s} leads={leads.filter((lead) => lead.status === s)} - onLeadClick={handleLeadClick} /> ))}
    {createPortal( - {activeLead && ( - - )} + {activeLead && } , document.body )}
    - - {selectedOpportunity && ( - setIsOpportunityModalOpen(false)} - data={selectedOpportunity} - /> - )} - {isContactModalOpen && ( - setIsContactModalOpen(false)} - /> - )}
    ); }; From 22adad5245ac2fc21b9772b7011af56326d03efe Mon Sep 17 00:00:00 2001 From: mohamadsadem0 Date: Mon, 10 Feb 2025 18:06:45 +0200 Subject: [PATCH 21/23] . --- .../[locale]/crm/components/LeadsManager.tsx | 55 ++-- .../crm/components/OpportunityCalls.tsx | 42 +++ .../crm/components/OpportunityCard.tsx | 67 ++--- .../crm/components/OpportunityDetails.tsx | 40 ++- .../components/OpportunityDetailsModal.tsx | 251 ++---------------- .../crm/components/OpportunityTabs.tsx | 31 +++ .../crm/components/opportunityData.ts | 20 ++ src/types/Module.tsx | 22 +- 8 files changed, 200 insertions(+), 328 deletions(-) create mode 100644 src/app/[locale]/crm/components/OpportunityCalls.tsx create mode 100644 src/app/[locale]/crm/components/OpportunityTabs.tsx create mode 100644 src/app/[locale]/crm/components/opportunityData.ts diff --git a/src/app/[locale]/crm/components/LeadsManager.tsx b/src/app/[locale]/crm/components/LeadsManager.tsx index ac831e6..e2f06b6 100644 --- a/src/app/[locale]/crm/components/LeadsManager.tsx +++ b/src/app/[locale]/crm/components/LeadsManager.tsx @@ -7,20 +7,20 @@ import { updateLeadStatus, reorderLeads } from "@/redux/slices/leadsSlice"; import Leads from "./Leads"; import { DndContext, - DragOverEvent, DragOverlay, DragStartEvent, + DragOverEvent, } from "@dnd-kit/core"; import { createPortal } from "react-dom"; import LeadCard from "./LeadCard"; -import { Lead } from "@/redux/slices/leadsSlice"; import Header from "./Header"; +import { Lead } from "@/redux/slices/leadsSlice"; + +const statusColumns = ["new", "qualified", "proposition", "won"]; const LeadsManager: React.FC = () => { const dispatch = useDispatch(); const leads = useSelector((state: RootState) => state.leads.leads); - const status = ["new", "qualified", "proposition", "won"]; - const [activeLead, setActiveLead] = useState(null); const onDragStart = (event: DragStartEvent) => { @@ -31,58 +31,32 @@ const LeadsManager: React.FC = () => { const onDragOver = (event: DragOverEvent) => { const { active, over } = event; - if (!over) return; + if (!over || active.id === over.id) return; - const activeId = active.id; + const activeLeadId = active.id; const overId = over.id; + const activeIndex = leads.findIndex((lead) => lead.id === activeLeadId); + const overIndex = leads.findIndex((lead) => lead.id === overId); - if (activeId === overId) return; - - const isActiveLead = active.data.current?.type === "Lead"; - const isOverLead = over.data.current?.type === "Lead"; - const isOverStatus = over.data.current?.type === "Status"; - - if (!isActiveLead) return; - - if (isActiveLead && isOverLead) { - const activeIndex = leads.findIndex((lead) => lead.id === activeId); - const overIndex = leads.findIndex((lead) => lead.id === overId); - - ////dropping the lead over in the same column - if (leads[activeIndex].status !== leads[overIndex].status) { - dispatch( - updateLeadStatus({ id: +activeId, status: leads[overIndex].status }) - ); - } - + if (over.data.current?.type === "Lead") { dispatch(reorderLeads({ fromIndex: activeIndex, toIndex: overIndex })); - } - - ////dropping the lead over a different column - if (isActiveLead && isOverStatus) { - const activeIndex = leads.findIndex((lead) => lead.id === activeId); - - dispatch(updateLeadStatus({ id: +activeId, status: overId.toString() })); + } else if (over.data.current?.type === "Status") { + dispatch(updateLeadStatus({ id: +activeLeadId, status: overId.toString() })); } }; return (
    -
    - {status.map((s) => ( - lead.status === s)} - /> + {statusColumns.map((status) => ( + lead.status === status)} /> ))}
    {createPortal( - {activeLead && } + {activeLead && } , document.body )} @@ -92,3 +66,4 @@ const LeadsManager: React.FC = () => { }; export default LeadsManager; + \ No newline at end of file diff --git a/src/app/[locale]/crm/components/OpportunityCalls.tsx b/src/app/[locale]/crm/components/OpportunityCalls.tsx new file mode 100644 index 0000000..67adbd4 --- /dev/null +++ b/src/app/[locale]/crm/components/OpportunityCalls.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { DataTable } from "primereact/datatable"; +import { Column } from "primereact/column"; +import { Button } from "primereact/button"; + +interface Call { + id: number; + salesRep: string; + callDate: string; + duration: string; + outcome: string; + note: string; +} + +interface OpportunityCallsProps { + calls: Call[]; + onAddCall: () => void; +} + +const OpportunityCalls: React.FC = ({ calls, onAddCall }) => { + return ( +
    +
    + ); +}; + +export default OpportunityCalls; diff --git a/src/app/[locale]/crm/components/OpportunityCard.tsx b/src/app/[locale]/crm/components/OpportunityCard.tsx index e14c55b..a4cdb33 100644 --- a/src/app/[locale]/crm/components/OpportunityCard.tsx +++ b/src/app/[locale]/crm/components/OpportunityCard.tsx @@ -1,65 +1,34 @@ -'use client'; +"use client"; -import React, { useState } from 'react'; -import OpportunityDetailsModal from './OpportunityDetailsModal'; +import React, { useState } from "react"; +import OpportunityDetailsModal from "./OpportunityDetailsModal"; +import { Opportunity } from "@/types/Module"; -interface Opportunity { - id: number; - leadId: number; - name: string; - phone: string; - email: string; - source: string; - industry: string; - assignedTo: string; - status: string; - nextFollowUp: string; - dealValue: string; - closingDate: string; - dealStatus: string; - createdAt: string; - updatedAt: string; - note: string; +interface OpportunityCardProps { + opportunity: Opportunity; } -const sampleOpportunity: Opportunity = { - id: 1, - leadId: 20, - name: 'Lead Name Here', - phone: '+961 70 123 456', - email: 'leademployee@gmail.com', - source: 'Lead source here', - industry: 'Trading', - assignedTo: 'John Smith', - status: 'Completed', - nextFollowUp: 'Feb 20,2025', - dealValue: '$10,000', - closingDate: 'Mar 30,2025', - dealStatus: 'Won', - createdAt: 'Jan 20,2025', - updatedAt: 'Jan 25,2025', - note: 'Lorem ipsum dolor sit amet consectetur. Fusce morbi quis suspendisse massa dictum in eleifend bibendum. Aliquet mollis sem nulla vestibulum.', -}; - -const OpportunityCard: React.FC = () => { +const OpportunityCard: React.FC = ({ opportunity }) => { const [isOpen, setIsOpen] = useState(false); return ( <>
    setIsOpen(true)} - className="border rounded p-4 cursor-pointer hover:shadow-md" + className="border rounded p-4 cursor-pointer hover:shadow-md transition" > -

    Opportunity #{sampleOpportunity.id}

    -

    {sampleOpportunity.name}

    -

    {sampleOpportunity.phone}

    +

    Opportunity #{opportunity.id}

    +

    {opportunity.name}

    +

    {opportunity.phone}

    - setIsOpen(false)} - data={sampleOpportunity} - /> + {isOpen && ( + setIsOpen(false)} + data={opportunity} + /> + )} ); }; diff --git a/src/app/[locale]/crm/components/OpportunityDetails.tsx b/src/app/[locale]/crm/components/OpportunityDetails.tsx index c4cbd80..d561bf1 100644 --- a/src/app/[locale]/crm/components/OpportunityDetails.tsx +++ b/src/app/[locale]/crm/components/OpportunityDetails.tsx @@ -1,5 +1,41 @@ import React from "react"; +import { Tag } from "primereact/tag"; +import { Opportunity } from "@/types/Module"; -export default function OpportunityDetails() { - return
    ; +interface OpportunityDetailsProps { + data: Opportunity; } + +const OpportunityDetails: React.FC = ({ data }) => { + return ( +
    +
    +
    +

    ID: {data.id}

    +

    Lead ID: {data.leadId}

    +

    Name: {data.name}

    +

    Phone: {data.phone}

    +

    Email: {data.email}

    +

    Note:

    +
    {data.note}
    +
    +
    +

    Source: {data.source}

    +

    Industry: {data.industry}

    +

    Assigned to: {data.assignedTo}

    +

    Status:

    +

    Next Follow-up: {data.nextFollowUp}

    +
    +
    +

    Deal Value: {data.dealValue}

    +

    Closing Date: {data.closingDate}

    +

    Deal Status:

    +

    Created at: {data.createdAt}

    +

    Updated at: {data.updatedAt}

    +
    +
    +
    + ); +}; + +export default OpportunityDetails; diff --git a/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx b/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx index 1143d9a..b3c74ec 100644 --- a/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx +++ b/src/app/[locale]/crm/components/OpportunityDetailsModal.tsx @@ -2,35 +2,11 @@ import React, { useState } from "react"; import { Dialog } from "primereact/dialog"; -import { TabView, TabPanel } from "primereact/tabview"; -import { DataTable } from "primereact/datatable"; -import { Column } from "primereact/column"; import { Button } from "primereact/button"; -import { Tag } from "primereact/tag"; -import AddCallModal from "./AddCallModal"; // Ensure the correct path - -import "primereact/resources/themes/saga-blue/theme.css"; -import "primereact/resources/primereact.min.css"; -import "primeicons/primeicons.css"; - -interface Opportunity { - id: number; - leadId: number; - name: string; - phone: string; - email: string; - source: string; - industry: string; - assignedTo: string; - status: string; - nextFollowUp: string; - dealValue: string; - closingDate: string; - dealStatus: string; - createdAt: string; - updatedAt: string; - note: string; -} +import OpportunityDetails from "./OpportunityDetails"; +import OpportunityTabs from "./OpportunityTabs"; +import AddCallModal from "./AddCallModal"; +import { Opportunity } from "@/types/Module"; interface OpportunityDetailsModalProps { isOpen: boolean; @@ -38,55 +14,14 @@ interface OpportunityDetailsModalProps { data: Opportunity; } -const OpportunityDetailsModal: React.FC = ({ - isOpen, - onClose, - data, -}) => { +const OpportunityDetailsModal: React.FC = ({ isOpen, onClose, data }) => { const [activeIndex, setActiveIndex] = useState(0); const [isAddCallModalOpen, setIsAddCallModalOpen] = useState(false); const mockCalls = [ - { - id: 1, - salesRep: "Name Here", - callDate: "Jan 20,2025", - duration: "1 Hour", - outcome: "00000", - note: "Lorem ipsum dolor sit amet consectetur...", - }, - { - id: 2, - salesRep: "Name Here", - callDate: "Jan 20,2025", - duration: "30 Min", - outcome: "00000", - note: "Lorem ipsum dolor sit amet consectetur...", - }, - { - id: 3, - salesRep: "Name Here", - callDate: "Jan 20,2025", - duration: "45 Min", - outcome: "00000", - note: "Lorem ipsum dolor sit amet consectetur...", - }, - { - id: 4, - salesRep: "Name Here", - callDate: "Jan 20,2025", - duration: "20 Min", - outcome: "00000", - note: "Lorem ipsum dolor sit amet consectetur...", - }, - { - id: 5, - salesRep: "Name Here", - callDate: "Jan 20,2025", - duration: "1 Hour, 10 Min", - outcome: "00000", - note: "Lorem ipsum dolor sit amet consectetur...", - }, + { id: 1, salesRep: "Name Here", callDate: "Jan 20,2025", duration: "1 Hour", outcome: "00000", note: "Lorem ipsum dolor sit amet..." }, + { id: 2, salesRep: "Name Here", callDate: "Jan 20,2025", duration: "30 Min", outcome: "00000", note: "Lorem ipsum dolor sit amet..." }, + { id: 3, salesRep: "Name Here", callDate: "Jan 20,2025", duration: "45 Min", outcome: "00000", note: "Lorem ipsum dolor sit amet..." }, ]; return ( @@ -94,182 +29,26 @@ const OpportunityDetailsModal: React.FC = ({ {/* Header */}
    General Details -
    {/* Main Content */}
    - {/* Details Section */} -
    -
    -
    -

    - ID: {data.id} -

    -

    - Lead ID: {data.leadId} -

    -

    - Name: {data.name} -

    -

    - Phone: {data.phone} -

    -

    - Email: {data.email} -

    -

    - Note: -

    -
    {data.note}
    -
    -
    -

    - Source: {data.source} -

    -

    - Industry: {data.industry} -

    -

    - Assigned to: {data.assignedTo} -

    -

    - Status:{" "} - -

    -

    - Next Follow-up: {data.nextFollowUp} -

    -
    -
    -

    - Deal Value: {data.dealValue} -

    -

    - Closing Date: {data.closingDate} -

    -

    - Deal Status:{" "} - -

    -

    - Created at: {data.createdAt} -

    -

    - Updated at: {data.updatedAt} -

    -
    -
    -
    - - {/* Tabs Section */} - setActiveIndex(e.index)} - className="mt-6" - > - -
    -
    -
    - - -
    - Follow-up content here... -
    -
    - - -
    - Meetings content here... -
    -
    - - -
    - Notes content here... -
    -
    -
    + + setIsAddCallModalOpen(true)} />
    - {/* AddCallModal rendered on top */} - setIsAddCallModalOpen(false)} - /> + {/* Add Call Modal */} + setIsAddCallModalOpen(false)} /> ); }; diff --git a/src/app/[locale]/crm/components/OpportunityTabs.tsx b/src/app/[locale]/crm/components/OpportunityTabs.tsx new file mode 100644 index 0000000..5462e2a --- /dev/null +++ b/src/app/[locale]/crm/components/OpportunityTabs.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { TabView, TabPanel } from "primereact/tabview"; +import OpportunityCalls from "./OpportunityCalls"; + +interface OpportunityTabsProps { + activeIndex: number; + onTabChange: (index: number) => void; + calls: any[]; + onAddCall: () => void; +} + +const OpportunityTabs: React.FC = ({ activeIndex, onTabChange, calls, onAddCall }) => { + return ( + onTabChange(e.index)} className="mt-6"> + + + + +
    Follow-up content here...
    +
    + +
    Meetings content here...
    +
    + +
    Notes content here...
    +
    +
    + ); +}; + +export default OpportunityTabs; diff --git a/src/app/[locale]/crm/components/opportunityData.ts b/src/app/[locale]/crm/components/opportunityData.ts new file mode 100644 index 0000000..8f54a1a --- /dev/null +++ b/src/app/[locale]/crm/components/opportunityData.ts @@ -0,0 +1,20 @@ +import { Opportunity } from "@/types/Module"; + +export const sampleOpportunity: Opportunity = { + id: 1, + leadId: 20, + name: "Lead Name Here", + phone: "+961 70 123 456", + email: "leademployee@gmail.com", + source: "Lead source here", + industry: "Trading", + assignedTo: "John Smith", + status: "Completed", + nextFollowUp: "Feb 20, 2025", + dealValue: "$10,000", + closingDate: "Mar 30, 2025", + dealStatus: "Won", + createdAt: "Jan 20, 2025", + updatedAt: "Jan 25, 2025", + note: "Lorem ipsum dolor sit amet consectetur. Fusce morbi quis suspendisse massa dictum in eleifend bibendum. Aliquet mollis sem nulla vestibulum.", +}; diff --git a/src/types/Module.tsx b/src/types/Module.tsx index 4c4e017..61be9e9 100644 --- a/src/types/Module.tsx +++ b/src/types/Module.tsx @@ -15,4 +15,24 @@ export interface Attachment { path: string; fileName: string; type: string; -} \ No newline at end of file +} + +export interface Opportunity { + id: number; + leadId: number; + name: string; + phone: string; + email: string; + source: string; + industry: string; + assignedTo: string; + status: string; + nextFollowUp: string; + dealValue: string; + closingDate: string; + dealStatus: string; + createdAt: string; + updatedAt: string; + note: string; + } + \ No newline at end of file From a70f90884cbc1893bc6fa7f76bcab6b592e5ae5a Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Mon, 10 Feb 2025 20:16:08 +0200 Subject: [PATCH 22/23] fixed-dnd --- .../[locale]/crm/components/LeadsManager.tsx | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/app/[locale]/crm/components/LeadsManager.tsx b/src/app/[locale]/crm/components/LeadsManager.tsx index e2f06b6..8333704 100644 --- a/src/app/[locale]/crm/components/LeadsManager.tsx +++ b/src/app/[locale]/crm/components/LeadsManager.tsx @@ -31,17 +31,38 @@ const LeadsManager: React.FC = () => { const onDragOver = (event: DragOverEvent) => { const { active, over } = event; - if (!over || active.id === over.id) return; + if (!over) return; - const activeLeadId = active.id; + const activeId = active.id; const overId = over.id; - const activeIndex = leads.findIndex((lead) => lead.id === activeLeadId); - const overIndex = leads.findIndex((lead) => lead.id === overId); - if (over.data.current?.type === "Lead") { + if (activeId === overId) return; + + const isActiveLead = active.data.current?.type === "Lead"; + const isOverLead = over.data.current?.type === "Lead"; + const isOverStatus = over.data.current?.type === "Status"; + + if (!isActiveLead) return; + + if (isActiveLead && isOverLead) { + const activeIndex = leads.findIndex((lead) => lead.id === activeId); + const overIndex = leads.findIndex((lead) => lead.id === overId); + + ////dropping the lead over in the same column + if (leads[activeIndex].status !== leads[overIndex].status) { + dispatch( + updateLeadStatus({ id: +activeId, status: leads[overIndex].status }) + ); + } + dispatch(reorderLeads({ fromIndex: activeIndex, toIndex: overIndex })); - } else if (over.data.current?.type === "Status") { - dispatch(updateLeadStatus({ id: +activeLeadId, status: overId.toString() })); + } + + ////dropping the lead over a different column + if (isActiveLead && isOverStatus) { + const activeIndex = leads.findIndex((lead) => lead.id === activeId); + + dispatch(updateLeadStatus({ id: +activeId, status: overId.toString() })); } }; @@ -51,7 +72,11 @@ const LeadsManager: React.FC = () => {
    {statusColumns.map((status) => ( - lead.status === status)} /> + lead.status === status)} + /> ))}
    {createPortal( @@ -66,4 +91,3 @@ const LeadsManager: React.FC = () => { }; export default LeadsManager; - \ No newline at end of file From daefbc7a30fe290908e2042f54d58dbcf67d22e8 Mon Sep 17 00:00:00 2001 From: Hamzi Haidar Date: Tue, 11 Feb 2025 14:27:10 +0200 Subject: [PATCH 23/23] added-switching-views --- src/app/[locale]/crm/components/Header.tsx | 20 +++++++-- .../[locale]/crm/components/LeadsManager.tsx | 43 ++++++++++--------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/app/[locale]/crm/components/Header.tsx b/src/app/[locale]/crm/components/Header.tsx index c692dc1..c3c381a 100644 --- a/src/app/[locale]/crm/components/Header.tsx +++ b/src/app/[locale]/crm/components/Header.tsx @@ -4,7 +4,12 @@ import { InputText } from "primereact/inputtext"; import React, { useState } from "react"; import ContactModal from "./ContactModal"; -export default function Header() { +interface HeaderProps { + setActive: any; + active: string; +} + +export default function Header({ setActive, active }: HeaderProps) { const [isContactModalOpen, setIsContactModalOpen] = useState(false); return ( @@ -14,9 +19,18 @@ export default function Header() {