From e7f4bde6b24e7c64f861f769d4a3d9d01aeae8c0 Mon Sep 17 00:00:00 2001 From: Julien MAUCLAIR Date: Thu, 27 Nov 2025 14:20:45 +0100 Subject: [PATCH 1/9] refactor: clean up imports and simplify cell value formatting in Table component --- src/components/ui/Table.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/ui/Table.tsx b/src/components/ui/Table.tsx index d5a07e8..b68c1b1 100644 --- a/src/components/ui/Table.tsx +++ b/src/components/ui/Table.tsx @@ -1,7 +1,7 @@ "use client" -import React, { useEffect, useRef, useState } from 'react' -import { ChevronUp, ChevronDown, MoreHorizontal } from 'lucide-react' +import React, {useEffect, useRef, useState} from 'react' +import {MoreHorizontal} from 'lucide-react' import AvatarInitials from '@/components/ui/AvatarInitials' import Loading from '@/components/ui/Loading' @@ -67,8 +67,7 @@ const Badge: React.FC<{ const formatCellValue = (value: any) => { if (value === null || value === undefined) return '-' if (typeof value === 'string' && value.trim() === '') return '-' - const str = String(value) - return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() + return String(value) } // Table Row Component @@ -115,7 +114,7 @@ const TableRow = ({ {columns.map((column) => ( {column.avatar ? ( From 91c81726e558dacf8137e0671aee6bc1506beb32 Mon Sep 17 00:00:00 2001 From: Julien MAUCLAIR Date: Thu, 27 Nov 2025 14:22:46 +0100 Subject: [PATCH 2/9] fix: update description for AI settings in NavBar component --- src/components/layout/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/layout/NavBar.tsx b/src/components/layout/NavBar.tsx index 8b8d921..44715b6 100644 --- a/src/components/layout/NavBar.tsx +++ b/src/components/layout/NavBar.tsx @@ -55,7 +55,7 @@ export default function NavBar() { case 'settings/ai': return { title: 'AI Settings', - description: 'Edit the content and SEO of your page.' + description: 'Setup AI settings for your account.' }; case 'settings/users': return { From f98d954e8937f56e93c9c1cf4e47c19965c126e9 Mon Sep 17 00:00:00 2001 From: Julien MAUCLAIR Date: Thu, 27 Nov 2025 14:24:06 +0100 Subject: [PATCH 3/9] fix: update section title from "AI Settings" to "Mail Settings" in EmailForm component --- src/feature/settings/email/components/EmailForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/feature/settings/email/components/EmailForm.tsx b/src/feature/settings/email/components/EmailForm.tsx index 71913b3..530485d 100644 --- a/src/feature/settings/email/components/EmailForm.tsx +++ b/src/feature/settings/email/components/EmailForm.tsx @@ -40,7 +40,7 @@ export default function EmailForm() { )}
-

AI Settings

+

Mail Settings


E-mail Host
From 612806319ffec8c50b880012e664e93eb7e5e91e Mon Sep 17 00:00:00 2001 From: Julien MAUCLAIR Date: Thu, 27 Nov 2025 15:07:16 +0100 Subject: [PATCH 4/9] refactor: streamline imports and enhance form initialization with user session data --- src/feature/auth/api/apiUsers.ts | 1 - .../companies/components/CompaniesForm.tsx | 32 ++++--- .../customers/components/CustomersForm.tsx | 95 +++++++++++-------- .../prospects/components/ProspectForm.tsx | 84 ++++++++-------- 4 files changed, 113 insertions(+), 99 deletions(-) diff --git a/src/feature/auth/api/apiUsers.ts b/src/feature/auth/api/apiUsers.ts index 8347e69..042b0bf 100644 --- a/src/feature/auth/api/apiUsers.ts +++ b/src/feature/auth/api/apiUsers.ts @@ -5,7 +5,6 @@ import {prisma} from "@/libs/prisma"; type Params = { params: Promise<{ id: string }> }; - export async function GET(req: NextRequest, context: Params) { try { const session = await getServerSession(authOptions); diff --git a/src/feature/companies/components/CompaniesForm.tsx b/src/feature/companies/components/CompaniesForm.tsx index 3ff7653..7ae43a7 100644 --- a/src/feature/companies/components/CompaniesForm.tsx +++ b/src/feature/companies/components/CompaniesForm.tsx @@ -1,7 +1,7 @@ "use client" -import type React from "react" -import {useState, useEffect} from "react" +import React, {useEffect} from "react" +import {useState} from "react" import {useForm} from "react-hook-form" import {zodResolver} from "@hookform/resolvers/zod" import * as z from "zod" @@ -12,7 +12,7 @@ import SearchableDropdown from "@/components/ui/SearchableDropdown" import {Company} from "../types/types" import CustomDropdown from "@/components/ui/CustomDropdown" import {validateOwner} from "@/feature/forms/lib/formValidation" -import {useCompaniesStore} from "@/feature/companies/stores/useCompaniesStore"; +import {getSession, useSession} from "next-auth/react"; type CompanyFormValues = z.infer; @@ -28,15 +28,13 @@ const zodSchema = z.object({ phone: z .union([ z.literal(""), // chaîne vide acceptée - z.string().regex(/^\+?[0-9]{6,15}$/, { message: "Invalid phone number" }) + z.string().regex(/^\+?[0-9]{6,15}$/, {message: "Invalid phone number"}) ]) .optional(), owner: z .string() - .optional() - .refine(async (id) => !id || (await validateOwner(id)), { - message: "User does not exist", - }), + .trim() + .refine(async (id) => await validateOwner(id), {message: "User does not exist"}), tags: z.array(z.string().min(1)).max(10, "Up to 10 tags allowed").optional(), assign: z.array(z.string().min(1)).max(10, "Up to 10 assignments allowed").optional(), notes: z.string().optional(), @@ -75,10 +73,10 @@ export default function CompaniesForm({ const [assignInput, setAssignInput] = useState("") const [uploading, setUploading] = useState(false) const [uploadedFiles, setUploadedFiles] = useState([]) - + const {data: userData} = useSession(); // 🧩 Compute initial values like avant - const getInitialValues = (): CompanyFormValues => { + const getInitialValues = (userData: { user: { id: string } | null } | null): CompanyFormValues => { if (mode === "edit" && initialData) { return { fullName: initialData.fullName || "", @@ -116,8 +114,12 @@ export default function CompaniesForm({ notes: "", files: [], } + } else { + return { + ...initialValues, + owner: userData?.user?.id || "", + } } - return initialValues } const { @@ -129,13 +131,13 @@ export default function CompaniesForm({ watch, } = useForm({ resolver: zodResolver(zodSchema), - defaultValues: getInitialValues(), + defaultValues: getInitialValues(userData), }) const values = watch() useEffect(() => { - reset(getInitialValues()) + reset(getInitialValues(userData)) }, [mode, initialData]) const handleFileChange = async (e: React.ChangeEvent) => { @@ -237,12 +239,12 @@ export default function CompaniesForm({ setInput={setAssignInput} /> - + setValue("owner", val)} + onChange={(val) => setValue("owner", val, {shouldValidate: true})} placeholder="Select Owner" showIcon={false} maxOptions={20} diff --git a/src/feature/customers/components/CustomersForm.tsx b/src/feature/customers/components/CustomersForm.tsx index af3bd90..b704875 100644 --- a/src/feature/customers/components/CustomersForm.tsx +++ b/src/feature/customers/components/CustomersForm.tsx @@ -1,26 +1,27 @@ "use client" import type React from "react" -import { useEffect, useState } from "react" -import { useForm } from "react-hook-form" -import { z } from "zod" -import { zodResolver } from "@hookform/resolvers/zod" -import { Button } from "@/components/ui/Button" +import {useEffect, useState} from "react" +import {useForm} from "react-hook-form" +import {z} from "zod" +import {zodResolver} from "@hookform/resolvers/zod" +import {Button} from "@/components/ui/Button" import SearchableDropdown from "@/components/ui/SearchableDropdown" import TagInput from "@/components/ui/TagInput" import UploadButton from "@/components/ui/UploadButton" import CustomDropdown from "@/components/ui/CustomDropdown" -import { useCompaniesStore } from "@/feature/companies/stores/useCompaniesStore" -import { getCompanyOptions } from "@/feature/deals/libs/companyData" -import { Customer } from "../types/types" -import { validateCompany, validateOwner } from "@/feature/forms/lib/formValidation" +import {useCompaniesStore} from "@/feature/companies/stores/useCompaniesStore" +import {getCompanyOptions} from "@/feature/deals/libs/companyData" +import {Customer} from "../types/types" +import {validateCompany, validateOwner} from "@/feature/forms/lib/formValidation" +import {useSession} from "next-auth/react"; const customerSchema = z.object({ fullName: z.string().trim().min(1, "Full name is required"), company: z .string() .trim() - .refine(async (id) => await validateCompany(id), { message: "Company does not exist" }), + .refine(async (id) => await validateCompany(id), {message: "Company does not exist"}), email: z.email("Please enter a valid email address"), phone: z.union([ z.string().trim().regex(/^[\+]?[0-9\-\(\)\s]+$/, "Please enter a valid phone number"), @@ -29,11 +30,11 @@ const customerSchema = z.object({ status: z .string() .trim() - .refine((val) => ["Active", "FollowUp", "inactive"].includes(val), { message: "Status is required" }), + .refine((val) => ["Active", "FollowUp", "inactive"].includes(val), {message: "Status is required"}), owner: z .string() .trim() - .refine(async (id) => await validateOwner(id), { message: "User does not exist" }), + .refine(async (id) => await validateOwner(id), {message: "User does not exist"}), tags: z.array(z.string().trim().min(1)).max(10, "Up to 10 tags"), notes: z.string().optional().or(z.literal("")), files: z.array(z.instanceof(File)).optional(), @@ -42,13 +43,13 @@ const customerSchema = z.object({ type CustomerFormValues = z.infer export default function CustomerForm({ - onSubmit, - onCancel, - mode = "add", - initialData, - usersLoading: _usersLoading, - userOptions, -}: { + onSubmit, + onCancel, + mode = "add", + initialData, + usersLoading: _usersLoading, + userOptions, + }: { onSubmit: (values: CustomerFormValues) => void onCancel: () => void mode?: "add" | "edit" @@ -59,15 +60,16 @@ export default function CustomerForm({ const [tagInput, setTagInput] = useState("") const [uploading, setUploading] = useState(false) const [uploadedFiles, setUploadedFiles] = useState([]) - const { lastCompanyId } = useCompaniesStore() + const {lastCompanyId} = useCompaniesStore() + const {data: userData} = useSession(); useEffect(() => { useCompaniesStore.getState().fetchCompanies() }, []) - const getInitialValues = (): CustomerFormValues => { + const getInitialValues = (userData: { user: { id: string } | null } | null): CustomerFormValues => { if (mode === "edit" && initialData) { - const { companies } = useCompaniesStore.getState() + const {companies} = useCompaniesStore.getState() let companyValue = "" if (initialData.company) { @@ -123,20 +125,27 @@ export default function CustomerForm({ email: "", status: "", phone: "", - owner: userOptions.length > 0 ? userOptions[0].id : "", + owner: userData?.user ? userData.user.id : "", tags: [], notes: "", files: [], } } - const { register, handleSubmit, setValue, reset, watch, formState: { errors, isSubmitting } } = useForm({ + const { + register, + handleSubmit, + setValue, + reset, + watch, + formState: {errors, isSubmitting} + } = useForm({ resolver: zodResolver(customerSchema), - defaultValues: getInitialValues(), + defaultValues: getInitialValues(userData), }) useEffect(() => { - reset(getInitialValues()) + reset(getInitialValues(userData)) }, [initialData, mode, userOptions, lastCompanyId, reset]) const values = watch() @@ -148,7 +157,7 @@ export default function CustomerForm({ for (let i = 0; i < files.length; i++) form.append("file", files[i]) setUploading(true) - const res = await fetch("/api/uploadFile", { method: "POST", body: form }) + const res = await fetch("/api/uploadFile", {method: "POST", body: form}) setUploading(false) if (res.ok) { const json = await res.json() @@ -166,7 +175,7 @@ export default function CustomerForm({ } onSubmit(payload) if (mode === "add") { - reset(getInitialValues()) + reset(getInitialValues(userData)) setTagInput("") setUploadedFiles([]) } @@ -189,7 +198,7 @@ export default function CustomerForm({ name="company" value={values.company} options={getCompanyOptions()} - onChange={(val) => setValue("company", val, { shouldValidate: true })} + onChange={(val) => setValue("company", val, {shouldValidate: true})} placeholder="Search or create a company" /> @@ -217,7 +226,7 @@ export default function CustomerForm({ name="owner" value={values.owner} options={userOptions} - onChange={(val) => setValue("owner", val, { shouldValidate: true })} + onChange={(val) => setValue("owner", val, {shouldValidate: true})} placeholder="Select Owner" showIcon={false} maxOptions={20} @@ -227,12 +236,12 @@ export default function CustomerForm({ setValue("status", val, { shouldValidate: true })} + onChange={(val) => setValue("status", val, {shouldValidate: true})} placeholder="Select Status" options={[ - { value: "Active", label: "Active" }, - { value: "FollowUp", label: "Follow Up" }, - { value: "inactive", label: "Inactive" }, + {value: "Active", label: "Active"}, + {value: "FollowUp", label: "Follow Up"}, + {value: "inactive", label: "Inactive"}, ]} /> @@ -240,7 +249,7 @@ export default function CustomerForm({ setValue("tags", vals, { shouldValidate: true })} + setValue={(vals: string[]) => setValue("tags", vals, {shouldValidate: true})} input={tagInput} setInput={(value: string) => setTagInput(value)} /> @@ -255,7 +264,9 @@ export default function CustomerForm({ className="w-full text-sm resize-y shadow-sm rounded-md border border-[var(--border-gray)] bg-background px-3 py-2 outline-none focus:ring-1 focus:ring-gray-400 focus:outline-none" /> - setValue("files", vals, { shouldValidate: true })} uploading={uploading} uploadFile={(e) => handleFileChange(e)} /> + setValue("files", vals, {shouldValidate: true})} uploading={uploading} + uploadFile={(e) => handleFileChange(e)}/> {errors.files &&

{errors.files.message as string}

}
@@ -265,7 +276,7 @@ export default function CustomerForm({ type="button" className="flex-1" onClick={() => { - reset(getInitialValues()) + reset(getInitialValues(userData)) onCancel() }} > @@ -280,11 +291,11 @@ export default function CustomerForm({ } function FieldBlock({ - name, - label, - children, - error, -}: { + name, + label, + children, + error, + }: { name: string label: string children: React.ReactNode diff --git a/src/feature/prospects/components/ProspectForm.tsx b/src/feature/prospects/components/ProspectForm.tsx index 78cd446..2f07103 100644 --- a/src/feature/prospects/components/ProspectForm.tsx +++ b/src/feature/prospects/components/ProspectForm.tsx @@ -1,18 +1,19 @@ "use client" import type React from "react" -import { useEffect, useState } from "react" -import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" -import { z } from "zod" -import { Button } from "@/components/ui/Button" +import {useEffect, useState} from "react" +import {useForm} from "react-hook-form" +import {zodResolver} from "@hookform/resolvers/zod" +import {z} from "zod" +import {Button} from "@/components/ui/Button" import SearchableDropdown from "@/components/ui/SearchableDropdown" -import { useCompaniesStore } from "@/feature/companies/stores/useCompaniesStore" -import { Prospect } from "../types/types" -import { getCompanyOptions } from "../libs/companyData" +import {useCompaniesStore} from "@/feature/companies/stores/useCompaniesStore" +import {Prospect} from "../types/types" +import {getCompanyOptions} from "../libs/companyData" import TagInput from "@/components/ui/TagInput" import CustomDropdown from "@/components/ui/CustomDropdown" import {validateCompany, validateOwner} from "@/feature/forms/lib/formValidation"; +import {useSession} from "next-auth/react"; const prospectSchema = z.object({ fullName: z.string().trim().min(1, "Full name is required"), @@ -34,13 +35,13 @@ const prospectSchema = z.object({ .trim() .refine( (val) => ["New", "Qualified", "Converted", "Cold", "Warmlead", "Notintrested"].includes(val), - { message: "Status is required" } + {message: "Status is required"} ), owner: z.string().refine(async (id) => { return await validateOwner(id); }, { message: "User does not exist" - }), tags: z.array(z.string().trim().min(1)).max(10, "Up to 10 tags allowed"), + }), tags: z.array(z.string().trim().min(1)).max(10, "Up to 10 tags allowed"), notes: z.string().optional().or(z.literal("")), }) @@ -58,13 +59,13 @@ const baseInitialValues: ProspectFormValues = { } export default function ProspectForm({ - onSubmit, - onCancel, - mode = "add", - initialData, - usersLoading: _usersLoading, - userOptions, -}: { + onSubmit, + onCancel, + mode = "add", + initialData, + usersLoading: _usersLoading, + userOptions, + }: { onSubmit: (values: ProspectFormValues) => void onCancel: () => void mode?: "add" | "edit" @@ -73,15 +74,16 @@ export default function ProspectForm({ userOptions: { id: string; value: string; label: string }[] }) { const [tagInput, setTagInput] = useState("") - const { lastCompanyId } = useCompaniesStore(); + const {lastCompanyId} = useCompaniesStore(); + const {data: userData} = useSession(); useEffect(() => { useCompaniesStore.getState().fetchCompanies() }, []) - const getInitialValues = (): ProspectFormValues => { + const getInitialValues = (userData: { user: { id: string } | null } | null): ProspectFormValues => { if (mode === "edit" && initialData) { - const { companies } = useCompaniesStore.getState() + const {companies} = useCompaniesStore.getState() let companyValue = "" if (initialData.company) { @@ -127,7 +129,7 @@ export default function ProspectForm({ return { ...baseInitialValues, company: lastCompanyId || "", - owner: userOptions.length > 0 ? userOptions[0].id : "", + owner: userData?.user?.id || "", } } @@ -137,14 +139,14 @@ export default function ProspectForm({ setValue, reset, watch, - formState: { errors, isSubmitting }, + formState: {errors, isSubmitting}, } = useForm({ resolver: zodResolver(prospectSchema), - defaultValues: getInitialValues(), + defaultValues: getInitialValues(userData), }) useEffect(() => { - reset(getInitialValues()) + reset(getInitialValues(userData)) }, [initialData, mode, userOptions, reset]) const values = watch() @@ -158,7 +160,7 @@ export default function ProspectForm({ onSubmit(payload) if (mode === "add") { - reset(getInitialValues()) + reset(getInitialValues(userData)) setTagInput("") } }) @@ -180,7 +182,7 @@ export default function ProspectForm({ name="company" value={values.company} options={getCompanyOptions()} - onChange={(val) => setValue("company", val, { shouldValidate: true })} + onChange={(val) => setValue("company", val, {shouldValidate: true})} placeholder="Search or create a company" /> @@ -208,7 +210,7 @@ export default function ProspectForm({ name="owner" value={values.owner} options={userOptions} - onChange={(val) => setValue("owner", val, { shouldValidate: true })} + onChange={(val) => setValue("owner", val, {shouldValidate: true})} placeholder="Select Owner" showIcon={false} maxOptions={20} @@ -219,15 +221,15 @@ export default function ProspectForm({ setValue("status", val, { shouldValidate: true })} + onChange={(val) => setValue("status", val, {shouldValidate: true})} placeholder="Select Status" options={[ - { value: "New", label: "New" }, - { value: "Cold", label: "Cold" }, - { value: "Qualified", label: "Qualified" }, - { value: "Warmlead", label: "Warm Lead" }, - { value: "Converted", label: "Converted" }, - { value: "Notintrested", label: "Not Interested" }, + {value: "New", label: "New"}, + {value: "Cold", label: "Cold"}, + {value: "Qualified", label: "Qualified"}, + {value: "Warmlead", label: "Warm Lead"}, + {value: "Converted", label: "Converted"}, + {value: "Notintrested", label: "Not Interested"}, ]} /> @@ -235,7 +237,7 @@ export default function ProspectForm({ setValue("tags", vals, { shouldValidate: true })} + setValue={(vals: string[]) => setValue("tags", vals, {shouldValidate: true})} input={tagInput} setInput={(value: string) => setTagInput(value)} /> @@ -257,7 +259,7 @@ export default function ProspectForm({ type="button" className="flex-1" onClick={() => { - reset(getInitialValues()) + reset(getInitialValues(userData)) onCancel() }} > @@ -272,11 +274,11 @@ export default function ProspectForm({ } function FieldBlock({ - name, - label, - children, - error, -}: { + name, + label, + children, + error, + }: { name: string label: string children: React.ReactNode From 80478b10bb6449b3cb8cc140b7da63ee796f7319 Mon Sep 17 00:00:00 2001 From: JL-Perf Date: Thu, 27 Nov 2025 19:09:58 +0100 Subject: [PATCH 5/9] refactor: enhance Card component structure and styling; add color to pipeline stages --- src/components/ui/Card.tsx | 85 +++++++++++++++++-- .../dashboard/components/PipelineCard.tsx | 53 ++++++++---- src/feature/dashboard/types/Types.tsx | 10 ++- 3 files changed, 120 insertions(+), 28 deletions(-) diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx index 321017d..681ad98 100644 --- a/src/components/ui/Card.tsx +++ b/src/components/ui/Card.tsx @@ -1,23 +1,92 @@ -import * as React from "react"; -import { cn } from "@/libs/utils"; +import * as React from "react" -export function Card({ className, ...props }: React.HTMLAttributes) { +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return (
- ); + ) } -export function CardContent({ className, ...props }: React.HTMLAttributes) { +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { return (
- ); + ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, } diff --git a/src/feature/dashboard/components/PipelineCard.tsx b/src/feature/dashboard/components/PipelineCard.tsx index cf1cd4e..229b7f7 100644 --- a/src/feature/dashboard/components/PipelineCard.tsx +++ b/src/feature/dashboard/components/PipelineCard.tsx @@ -1,9 +1,9 @@ "use client" -import React, { useEffect, useState } from "react" -import { useRouter } from "next/navigation" +import React, {useEffect, useState} from "react" +import {useRouter} from "next/navigation" import Loading from "@/components/ui/Loading" -import type { PipelineChartData } from "../types/Types" +import type {PipelineChartData} from "../types/Types" const CHART_HEIGHT = 236 @@ -39,11 +39,11 @@ export function PipelineCard({ range = "this_month" }: { range?: string }) { const mappedData: PipelineChartData = { title: "Pipeline", stages: [ - { stage: "New Leads", count: stats.newDeals }, - { stage: "Contacted", count: stats.contactedDeals }, - { stage: "Proposal Sent", count: stats.proposalDeals }, - { stage: "Negotiation", count: stats.negotiationDeals }, - { stage: "Won", count: stats.wonDeals }, + {stage: "New Leads", count: stats.newDeals, color: "bg-purple-500"}, + {stage: "Contacted", count: stats.contactedDeals, color: "bg-purple-400"}, + {stage: "Proposal Sent", count: stats.proposalDeals, color: "bg-purple-300"}, + {stage: "Negotiation", count: stats.negotiationDeals, color: "bg-purple-200"}, + {stage: "Won", count: stats.wonDeals, color: "bg-purple-100"}, ], conversionRates: [ stats.totalDeals > 0 ? (stats.contactedDeals / stats.totalDeals) * 100 : 0, @@ -139,23 +139,44 @@ export function PipelineCard({ range = "this_month" }: { range?: string }) { ))}
-
+
{stages.map((stage, index) => { // Calculate bar height as percentage of max stage count (increased scaling for more prominent bars) const maxCount = Math.max(...stages.map(s => s.count)); - const barHeight = maxCount > 0 ? (stage.count / maxCount) * 250 : 0; // Increased from 150 to 250 for maximum prominence - + let barHeight = maxCount > 0 ? (stage.count / maxCount) * 250 : 0; // Increased from 150 to 250 for maximum prominence + // Hauteur actuelle + const h = Math.max(barHeight, 8); + + // hauteur du prochain (ou égal si dernier) + const nextH = + index < stages.length - 1 + ? ((stages[index + 1].count / maxCount) * 250) + : h; + // Déterminer le clip-path pour créer l'effet 3D + let clipPath = ""; + if (h < nextH) { + const percentageDiff = (Math.abs(nextH - h) / nextH) * 100; + // Si la barre actuelle est plus courte que la suivante, incliner vers la droite + clipPath = `polygon(0% ${percentageDiff}%, 100% 0%, 100% 100%, 0% 100%)`; + barHeight = nextH; + } else if (h > nextH) { + const percentageDiff = ((Math.abs(nextH - h) / h) * 100) - 2; + // Si la barre actuelle est plus haute que la suivante, incliner vers la gauche + clipPath = `polygon(0% 0%, 100% ${percentageDiff}%, 100% 100%, 0% 100%)`; + } else { + // Si les hauteurs sont égales, pas d'inclinaison + clipPath = `polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)`; + } return (
{/* Container for bottom-up growth */}
{/* 3D Effect Graph Bar - grows from bottom up */}
{/* 3D depth effect */} @@ -163,7 +184,7 @@ export function PipelineCard({ range = "this_month" }: { range?: string }) {
- +
); })} diff --git a/src/feature/dashboard/types/Types.tsx b/src/feature/dashboard/types/Types.tsx index fb4a158..7d3c249 100644 --- a/src/feature/dashboard/types/Types.tsx +++ b/src/feature/dashboard/types/Types.tsx @@ -1,5 +1,6 @@ import type React from "react" -import { ReactNode } from "react" +import {ReactNode} from "react" + export interface MetricData { title: string value: string @@ -11,12 +12,13 @@ export interface MetricData { export interface PipelineStage { stage: string count: number + color?: string } export interface PipelineChartData { - title: string - stages: PipelineStage[] - conversionRates: number[] + title: string + stages: PipelineStage[] + conversionRates: number[] } export interface Activity { From 4eea304814b1acec8807ca33ace30c1a4ff4fe5c Mon Sep 17 00:00:00 2001 From: Julien MAUCLAIR Date: Thu, 27 Nov 2025 18:15:10 +0000 Subject: [PATCH 6/9] Update README.md --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 85e0329..6359335 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,5 @@ -

Klickbee CRM

-

- - Due to GitHub Politics and the fact that [JMauclair](https://github.com/JMauclair) has been recently suspended for no reasons, all our projects will be only on [Stralya Gitea](https://git.stralya.com/Klickbee/klickbee-crm) - -

-

A demo is better than a thousand words From d4d5e0ae07d0865f3d75a5b2fec26251139d004b Mon Sep 17 00:00:00 2001 From: Julien MAUCLAIR Date: Fri, 28 Nov 2025 10:20:11 +0100 Subject: [PATCH 7/9] fix: update label from "Edit Deal" to "Edit Task" in index.tsx; correct import path in Card.tsx --- src/components/detailPage/index.tsx | 2 +- src/components/ui/Card.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/detailPage/index.tsx b/src/components/detailPage/index.tsx index a9855ed..2d31596 100644 --- a/src/components/detailPage/index.tsx +++ b/src/components/detailPage/index.tsx @@ -222,7 +222,7 @@ const DetailModal: React.FC = ({ ) : ( )} - {editLabel || "Edit Deal"} + {editLabel || "Edit Task"} )} {onReschedule && ( diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx index 681ad98..83fa76c 100644 --- a/src/components/ui/Card.tsx +++ b/src/components/ui/Card.tsx @@ -1,6 +1,6 @@ import * as React from "react" -import { cn } from "@/lib/utils" +import { cn } from "@/libs/utils"; function Card({ className, ...props }: React.ComponentProps<"div">) { return ( From 577f51b4d1b1011a8c74c88151c770d385017c27 Mon Sep 17 00:00:00 2001 From: Julien MAUCLAIR Date: Fri, 28 Nov 2025 10:20:21 +0100 Subject: [PATCH 8/9] refactor: simplify validation error messages and set default values for status and priority in TodoForm --- src/feature/todo/components/TodoForm.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/feature/todo/components/TodoForm.tsx b/src/feature/todo/components/TodoForm.tsx index 623d5a6..7fd6e9c 100644 --- a/src/feature/todo/components/TodoForm.tsx +++ b/src/feature/todo/components/TodoForm.tsx @@ -18,12 +18,10 @@ const todoSchema = z.object({ taskName: z.string().trim().min(1, "Task name is required"), linkedTo: z.string().trim().optional().default(""), assignedId: z.string().trim().optional().default(""), - status: z.enum(["to-do", "in-progress", "on-hold", "done"], { - errorMap: () => ({ message: "Status is required" }), - }), - priority: z.enum(["urgent", "high", "medium", "low"], { - errorMap: () => ({ message: "Priority is required" }), - }), + status: z.enum(["to-do", "in-progress", "on-hold", "done"], { message: "Status is required" }), + priority: z.enum(["urgent", "high", "medium", "low"], + { message: "Priority is required" }, + ), dueDate: z.string().optional(), notes: z.string().optional(), files: z.array(z.any()).optional(), @@ -35,8 +33,8 @@ const initialValues: TodoFormValues = { taskName: "", linkedTo: "", assignedId: "", - status: "", - priority: "", + status: "to-do", + priority: "low", dueDate: "", notes: "", files: [], @@ -82,8 +80,8 @@ export default function TodoForm({ ? "in-progress" : initialData.status === "OnHold" ? "on-hold" - : initialData.status.toLowerCase(), - priority: initialData.priority.toLowerCase(), + : initialData.status.toLowerCase() as "done", + priority: initialData.priority.toLowerCase() as "urgent" | "high" | "medium" | "low", dueDate: initialData.dueDate ? new Date(initialData.dueDate).toISOString().split("T")[0] : "", notes: initialData.notes || "", files: [], From bba89ca40b3feabcb615e64195df91040ddb4190 Mon Sep 17 00:00:00 2001 From: Julien MAUCLAIR Date: Fri, 28 Nov 2025 10:24:17 +0100 Subject: [PATCH 9/9] ci: add GitHub Actions workflow for build checks --- .gitea/workflows/build.yml | 24 ++++++++++++++++++++++++ .github/workflows/build.yml | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .gitea/workflows/build.yml create mode 100644 .github/workflows/build.yml diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..33d1dde --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,24 @@ +name: Build Check + +on: + push: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..df058be --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: Build Check + +on: + push: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run build + run: npm run build