From 36a8c0e24cb6724a1346245a6f4a15b352398de1 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Tue, 28 Apr 2026 21:53:26 +0330 Subject: [PATCH] feat(clients): refresh clients page layout and toast feedback --- src/components/CreateClientModal.tsx | 33 ++-- src/components/DeleteClientModal.tsx | 27 +-- src/components/EditClientModal.tsx | 29 +-- src/locales/en.ts | 11 +- src/locales/fa.ts | 11 +- src/pages/Clients.tsx | 262 +++++++++++++++------------ 6 files changed, 207 insertions(+), 166 deletions(-) diff --git a/src/components/CreateClientModal.tsx b/src/components/CreateClientModal.tsx index 3fbc1a4..4d6938e 100644 --- a/src/components/CreateClientModal.tsx +++ b/src/components/CreateClientModal.tsx @@ -1,5 +1,6 @@ -import { useState } from "react"; -import { createClient } from "../api/clients"; +import { useState } from "react"; +import { toast } from "sonner"; +import { createClient } from "../api/clients"; import { useTranslation } from "../hooks/useTranslation"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; @@ -22,18 +23,20 @@ export default function CreateClientModal({ isOpen, onClose, onSuccess, workspac const handleSubmit = async () => { if (!name.trim()) return; setIsLoading(true); - try { - await createClient(workspaceId, { name, notes }); - onSuccess(); - setName(""); - setNotes(""); - onClose(); - } catch (error) { - console.error(t.clients.errors.createFailed, error); - } finally { - setIsLoading(false); - } - }; + try { + await createClient(workspaceId, { name, notes }); + toast.success(t.clients.createSuccess); + onSuccess(); + setName(""); + setNotes(""); + onClose(); + } catch (error) { + console.error(t.clients.errors.createFailed, error); + toast.error(t.clients.errors.createFailed); + } finally { + setIsLoading(false); + } + }; const footer = ( <> @@ -72,4 +75,4 @@ export default function CreateClientModal({ isOpen, onClose, onSuccess, workspac ); -} \ No newline at end of file +} diff --git a/src/components/DeleteClientModal.tsx b/src/components/DeleteClientModal.tsx index e84caf1..594fdff 100644 --- a/src/components/DeleteClientModal.tsx +++ b/src/components/DeleteClientModal.tsx @@ -1,5 +1,6 @@ -import { useState } from "react"; -import { type Client } from "../types/client"; +import { useState } from "react"; +import { toast } from "sonner"; +import { type Client } from "../types/client"; import { deleteClient } from "../api/clients"; import { useTranslation } from "../hooks/useTranslation"; import { Button } from "./ui/button"; @@ -19,16 +20,18 @@ export default function DeleteClientModal({ isOpen, onClose, onSuccess, client } const handleDelete = async () => { if (!client) return; setIsLoading(true); - try { - await deleteClient(client.id); - onSuccess(); - onClose(); - } catch (error) { - console.error(t.clients.errors.deleteFailed, error); - } finally { - setIsLoading(false); - } - }; + try { + await deleteClient(client.id); + toast.success(t.clients.deleteSuccess); + onSuccess(); + onClose(); + } catch (error) { + console.error(t.clients.errors.deleteFailed, error); + toast.error(t.clients.errors.deleteFailed); + } finally { + setIsLoading(false); + } + }; const footer = ( <> diff --git a/src/components/EditClientModal.tsx b/src/components/EditClientModal.tsx index 218969e..287ad3b 100644 --- a/src/components/EditClientModal.tsx +++ b/src/components/EditClientModal.tsx @@ -1,5 +1,6 @@ -import { useState, useEffect } from "react"; -import { type Client } from "../types/client"; +import { useState, useEffect } from "react"; +import { toast } from "sonner"; +import { type Client } from "../types/client"; import { updateClient } from "../api/clients"; import { useTranslation } from "../hooks/useTranslation"; import { Button } from "./ui/button"; @@ -30,16 +31,18 @@ export default function EditClientModal({ isOpen, onClose, onSuccess, client }: const handleSubmit = async () => { if (!client || !name.trim()) return; setIsLoading(true); - try { - await updateClient(client.id, { name, notes }); - onSuccess(); - onClose(); - } catch (error) { - console.error(t.clients.errors.updateFailed, error); - } finally { - setIsLoading(false); - } - }; + try { + await updateClient(client.id, { name, notes }); + toast.success(t.clients.updateSuccess); + onSuccess(); + onClose(); + } catch (error) { + console.error(t.clients.errors.updateFailed, error); + toast.error(t.clients.errors.updateFailed); + } finally { + setIsLoading(false); + } + }; const footer = ( <> @@ -78,4 +81,4 @@ export default function EditClientModal({ isOpen, onClose, onSuccess, client }: ); -} \ No newline at end of file +} diff --git a/src/locales/en.ts b/src/locales/en.ts index 1917c42..985abbf 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -244,10 +244,13 @@ export const en = { editClient: "Edit Client", deleteConfirmTitle: "Delete Client", deleteConfirmMessage: (name: string) => `Are you sure you want to delete ${name}?`, - delete: "Delete", - saveChanges: "Save Changes", - errors: { - createFailed: "Failed to create client", + delete: "Delete", + saveChanges: "Save Changes", + createSuccess: "Client created successfully.", + updateSuccess: "Client updated successfully.", + deleteSuccess: "Client deleted successfully.", + errors: { + createFailed: "Failed to create client", fetchFailed: "Failed to fetch clients", updateFailed: "Failed to update client", deleteFailed: "Failed to delete client", diff --git a/src/locales/fa.ts b/src/locales/fa.ts index ecb4ae1..d982c8b 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -241,10 +241,13 @@ export const fa = { editClient: "ویرایش مشتری", deleteConfirmTitle: "حذف مشتری", deleteConfirmMessage: (name: string) => `آیا از حذف ${name} اطمینان دارید؟`, - delete: "حذف", - saveChanges: "ذخیره تغییرات", - errors: { - createFailed: "خطا در ایجاد مشتری", + delete: "حذف", + saveChanges: "ذخیره تغییرات", + createSuccess: "مشتری با موفقیت ایجاد شد.", + updateSuccess: "مشتری با موفقیت به‌روزرسانی شد.", + deleteSuccess: "مشتری با موفقیت حذف شد.", + errors: { + createFailed: "خطا در ایجاد مشتری", fetchFailed: "خطا در دریافت لیست مشتری‌ها", updateFailed: "خطا در ویرایش مشتری", deleteFailed: "خطا در حذف مشتری", diff --git a/src/pages/Clients.tsx b/src/pages/Clients.tsx index 33d2ac2..b335764 100644 --- a/src/pages/Clients.tsx +++ b/src/pages/Clients.tsx @@ -1,5 +1,6 @@ -import { useEffect, useState } from "react" -import { Plus, Building2, Loader2, Pencil, Trash2 } from "lucide-react" +import { useEffect, useState } from "react" +import { Plus, Building2, Loader2, Pencil, Trash2 } from "lucide-react" +import { toast } from "sonner" import { useWorkspace } from "../context/WorkspaceContext" import { useAppContext } from "../context/AppContext" import { useTranslation } from "../hooks/useTranslation" @@ -13,11 +14,11 @@ import { type Client } from "../types/client" import { getClients } from "../api/clients" import CreateClientModal from "../components/CreateClientModal" import EditClientModal from "../components/EditClientModal" -import DeleteClientModal from "../components/DeleteClientModal" -import FilterBar from "../components/FilterBar" -import { Button } from "../components/ui/button" -import { Card } from "../components/ui/card" -import { Pagination } from "../components/Pagination" +import DeleteClientModal from "../components/DeleteClientModal" +import FilterBar from "../components/FilterBar" +import { Button } from "../components/ui/button" +import { Card, CardContent, CardTitle } from "../components/ui/card" +import { Pagination } from "../components/Pagination" export default function Clients() { const { activeWorkspace } = useWorkspace() @@ -82,11 +83,12 @@ export default function Clients() { setClients(items) setTotalItems(count) - } catch (error) { - console.error(t.clients.errors.fetchFailed, error) - setClients([]) - } finally { - setIsLoading(false) + } catch (error) { + console.error(t.clients.errors.fetchFailed, error) + toast.error(t.clients.errors.fetchFailed) + setClients([]) + } finally { + setIsLoading(false) } } @@ -107,122 +109,146 @@ export default function Clients() { fetchClientsList() }, [activeWorkspace?.id, debouncedSearch, ordering, currentPage, limit]) - if (!activeWorkspace) { - return ( -
- {t.clients.selectWorkspace} -
- ) - } - - return ( -
-
-
-

{t.clients.title}

-

- {t.clients.description(activeWorkspace.name)} -

-
- - {canCreateClient && ( - - )} -
- - - - -
- {isLoading ? ( -
- -
- ) : clients.length === 0 ? ( -
- -

{t.clients.noClients}

-

- {searchQuery ? t.clients.noClientsSearch : t.clients.noClientsAdd} -

-
- ) : ( -
    + if (!activeWorkspace) { + return ( +
    +
    + {t.clients.selectWorkspace} +
    +
    + ) + } + + return ( +
    +
    +
    +
    +

    {t.clients.title}

    +

    + {t.clients.description(activeWorkspace.name)} +

    +
    + + {canCreateClient && ( + + )} +
    +
    + +
    + +
    + + {isLoading ? ( +
    +
    + +
    +
    + ) : ( +
    + {clients.length === 0 ? ( +
    + +

    {t.clients.noClients}

    +

    + {searchQuery ? t.clients.noClientsSearch : t.clients.noClientsAdd} +

    +
    + ) : ( +
    {clients.map((client) => { const canDeleteClient = canDeleteWorkspaceResource({ workspaceRole, currentUserId: user?.id, createdById: client.created_by?.id, }) + return ( -
  • -
    -

    {client.name}

    - {client.notes && ( -

    - {client.notes} -

    - )} -
    - - {(canEditClient || canDeleteClient) && ( -
    - {canEditClient && ( - - )} - {canDeleteClient && ( - - )} -
    - )} -
  • + + +
    +
    +
    + {client.name.trim().charAt(0).toUpperCase() || "C"} +
    +
    + {client.name} +
    +
    + + {(canEditClient || canDeleteClient) && ( +
    + {canEditClient && ( + + )} + {canDeleteClient && ( + + )} +
    + )} +
    + +
    +

    + {client.notes || t.workspace?.noDescription || "No description"} +

    +
    + {t.clients.addedOn}: {formatDate(client.created_at)} +
    +
    +
    +
    ) })} -
+
)} -
- - - {!isLoading && clients.length > 0 && ( - - )} - - {canCreateClient && ( - 0 && ( + + )} + + )} + + {canCreateClient && ( + setIsCreateModalOpen(false)} onSuccess={fetchClientsList}