From 8e263e364a261f2b0eaea6a837e778902f947592 Mon Sep 17 00:00:00 2001 From: Shoaib96978 Date: Sat, 13 Jun 2026 11:54:18 +0500 Subject: [PATCH 1/2] feat: integrate react-hot-toast for notifications and update case handling --- package-lock.json | 28 +++++++++++++++++- package.json | 1 + src/App.tsx | 15 ++++++---- src/features/cases/api/case-api.ts | 3 +- .../cases/components/CaseDetailsModal.tsx | 10 +++---- .../cases/components/case-list/CaseList.tsx | 2 -- src/features/cases/hooks/useCases.ts | 24 ++++++++------- src/features/documents/hooks/useDocuments.ts | 13 +++++++-- src/features/followup/hooks/useFollowups.ts | 17 +++++++---- src/lib/toast.service.ts | 29 +++++++++++++++++++ 10 files changed, 108 insertions(+), 34 deletions(-) create mode 100644 src/lib/toast.service.ts diff --git a/package-lock.json b/package-lock.json index bee4696..67359f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "react-hook-form": "^7.72.1", + "react-hot-toast": "^2.6.0", "react-router-dom": "^7.14.1", "zod": "^4.3.6", "zustand": "^5.0.12" @@ -1658,7 +1659,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -2228,6 +2228,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz", + "integrity": "sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3063,6 +3072,23 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-router": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.1.tgz", diff --git a/package.json b/package.json index 9d50695..8ed03d3 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "react-hook-form": "^7.72.1", + "react-hot-toast": "^2.6.0", "react-router-dom": "^7.14.1", "zod": "^4.3.6", "zustand": "^5.0.12" diff --git a/src/App.tsx b/src/App.tsx index 1b7c9d3..6a52748 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,19 @@ -import { useIsFetching, useIsMutating } from '@tanstack/react-query' +// import { useIsFetching, useIsMutating } from '@tanstack/react-query' import { RouterProvider } from 'react-router-dom' import { useEffect } from 'react' -import { Loader } from './shared/components/Loader' +// import { Loader } from './shared/components/Loader' import { router } from './router' import { useAuthStore } from './store/authStore' import { getMe, refreshToken } from './features/auth/api/auth.api' +import { Toaster } from 'react-hot-toast' const App = () => { - const isFetching = useIsFetching() - const isMutating = useIsMutating() + + // const isFetching = useIsFetching() + // const isMutating = useIsMutating() const { setAuth, setAccessToken, accessToken } = useAuthStore() - const isLoading = isFetching > 0 || isMutating > 0 + // const isLoading = isFetching > 0 || isMutating > 0 useEffect(() => { const initAuth = async () => { @@ -69,7 +71,8 @@ const App = () => { return ( <> - {isLoading && } + + {/* {isLoading && } */} ) diff --git a/src/features/cases/api/case-api.ts b/src/features/cases/api/case-api.ts index 8a8be19..8146d6a 100644 --- a/src/features/cases/api/case-api.ts +++ b/src/features/cases/api/case-api.ts @@ -22,13 +22,12 @@ const withTotalPages = ( // ── Get All Cases (paginated) ───────────────────────────────────── export const getCases = async ( - organizationId: string, page: number = 1, pageSize: number = 10 ): Promise>> => { const response = await AxiosInstance.get>>( '/Case/getAll', - { params: { organizationId, page, pageSize } } + { params: { page, pageSize } } ); return withTotalPages(response.data); }; diff --git a/src/features/cases/components/CaseDetailsModal.tsx b/src/features/cases/components/CaseDetailsModal.tsx index c29ffb7..c097bd4 100644 --- a/src/features/cases/components/CaseDetailsModal.tsx +++ b/src/features/cases/components/CaseDetailsModal.tsx @@ -1,8 +1,8 @@ import '../styles/case-details-modal.css'; -import type { GetCaseDto } from '../types/case.types'; +import type { CaseDto } from '../types/case.types'; interface Props { - caseItem: GetCaseDto; + caseItem: CaseDto; onEdit: () => void; onClose: () => void; } @@ -86,10 +86,10 @@ export default function CaseDetailsModal({ caseItem, onEdit, onClose }: Props) {
Petitioners
{caseItem.petitioners?.length > 0 ? (
- {caseItem.petitioners.map((name) => ( - + {caseItem.petitioners.map(p => ( + - {name} + {p.name} ))}
diff --git a/src/features/cases/components/case-list/CaseList.tsx b/src/features/cases/components/case-list/CaseList.tsx index 688972c..ff93aa1 100644 --- a/src/features/cases/components/case-list/CaseList.tsx +++ b/src/features/cases/components/case-list/CaseList.tsx @@ -1,5 +1,4 @@ import { useState, useCallback } from 'react'; - import CasePageHeader from './CasePageHeader'; import CaseStatsBar from './CaseStatsBar'; import CaseFilterTabs from './CaseFilterTabs'; @@ -8,7 +7,6 @@ import CaseTable from './CaseTable'; import CaseDrawer from './CaseDrawer'; import CreateCaseModal from '../create-case/CreateCaseModal'; import CaseDetailsModal from '../CaseDetailsModal'; - import { useGetAllCases, useSearchCases, useDeleteCase } from '../../hooks/useCases'; import { useCaseModal } from '../../hooks/useCaseModal'; import type { CaseDto, SearchParams } from '../../types/case.types'; // ✅ CaseDto only, no GetCaseDto diff --git a/src/features/cases/hooks/useCases.ts b/src/features/cases/hooks/useCases.ts index b1e3d7b..35b2aef 100644 --- a/src/features/cases/hooks/useCases.ts +++ b/src/features/cases/hooks/useCases.ts @@ -4,25 +4,22 @@ import { createCase, deleteCase, getCases, searchCases } from '../api/case-api'; import { usePetitioners } from '../../petitioners/hooks/usePetitioners'; import { useGetDepartments } from '../../departments/hooks/useDepartments'; import { useGetCourts } from '../../courts/hooks/useCourts'; -import { useAuthStore } from '../../../store/authStore'; +import { toastService } from '../../../lib/toast.service' + // ── Query Key Factory ───────────────────────────────────────────── export const caseKeys = { all: () => ['cases'] as const, - list: (orgId: string, page: number, size: number) => ['cases', 'list', orgId, page, size] as const, + list: (page: number, size: number) => ['cases', 'list', page, size] as const, search: (params: SearchParams) => ['cases', 'search', params] as const, }; // ── Cases ───────────────────────────────────────────────────────── export const useGetAllCases = (page: number = 1, pageSize: number = 10) => { - const { user } = useAuthStore(); - const orgId = user?.organizationId ?? ''; - return useQuery({ - queryKey: caseKeys.list(orgId, page, pageSize), - queryFn: () => getCases(orgId, page, pageSize), - enabled: !!orgId, + queryKey: caseKeys.list(page, pageSize), + queryFn: () => getCases(page, pageSize), }); }; @@ -37,14 +34,19 @@ export const useSearchCases = (params: SearchParams) => { export const useCreateCase = () => { const qc = useQueryClient(); - const { user } = useAuthStore(); + // const { user } = useAuthStore(); return useMutation({ mutationFn: (data: CreateCaseDto) => createCase({ ...data, - organizationId: user?.organizationId ?? data.organizationId, }), - onSuccess: () => qc.invalidateQueries({ queryKey: caseKeys.all() }), + onSuccess: (response) => { + toastService.success(response.message); + qc.invalidateQueries({ queryKey: caseKeys.all() }); + }, + onError: (error: unknown) => { + toastService.error(error); + } }); }; diff --git a/src/features/documents/hooks/useDocuments.ts b/src/features/documents/hooks/useDocuments.ts index 62714ad..8116152 100644 --- a/src/features/documents/hooks/useDocuments.ts +++ b/src/features/documents/hooks/useDocuments.ts @@ -1,6 +1,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { documentsApi } from '../api/documents' import type { UploadDocumentDto } from '../types' +import { toastService } from '../../../lib/toast.service' const DOCUMENT_KEYS = { byCase: (caseId: string) => ['documents', 'case', caseId], @@ -28,11 +29,15 @@ export function useUploadDocument(caseId: string) { return useMutation({ mutationFn: (dto: UploadDocumentDto) => documentsApi.upload(dto), - onSuccess: () => { + onSuccess: (response) => { + toastService.success(response.message); queryClient.invalidateQueries({ queryKey: DOCUMENT_KEYS.byCase(caseId), }) }, + onError: (error: unknown) => { + toastService.error(error); + } }) } @@ -41,10 +46,14 @@ export function useDeleteDocument(caseId: string) { return useMutation({ mutationFn: (id: string) => documentsApi.delete(id), - onSuccess: () => { + onSuccess: (response) => { + toastService.success(response.message); queryClient.invalidateQueries({ queryKey: DOCUMENT_KEYS.byCase(caseId), }) }, + onError: (error: unknown) => { + toastService.error(error); + } }) } \ No newline at end of file diff --git a/src/features/followup/hooks/useFollowups.ts b/src/features/followup/hooks/useFollowups.ts index 2237577..4f4416e 100644 --- a/src/features/followup/hooks/useFollowups.ts +++ b/src/features/followup/hooks/useFollowups.ts @@ -1,8 +1,7 @@ -// hooks/useFollowups.ts - import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { followupApi } from '../api/followupApi' import type { CreateFollowUpDto, UpdateFollowUpDto, FollowUpPageParams } from '../types/followup.types' +import { toastService } from '../../../lib/toast.service' export const followupKeys = { all: ['followups'] as const, @@ -30,9 +29,13 @@ export const useCreateFollowUp = () => { const qc = useQueryClient() return useMutation({ mutationFn: (data: CreateFollowUpDto) => followupApi.create(data), - onSuccess: () => { + onSuccess: (response) => { + toastService.success(response.message); qc.invalidateQueries({ queryKey: followupKeys.all }) }, + onError: (error: unknown) => { + toastService.error(error); + } }) } @@ -50,8 +53,12 @@ export const useDeleteFollowUp = () => { const qc = useQueryClient() return useMutation({ mutationFn: (id: string) => followupApi.delete(id), - onSuccess: () => { + onSuccess: (response) => { + toastService.success(response.message); qc.invalidateQueries({ queryKey: followupKeys.all }) }, - }) + onError: (error: unknown) => { + toastService.error(error); + } + }); } \ No newline at end of file diff --git a/src/lib/toast.service.ts b/src/lib/toast.service.ts new file mode 100644 index 0000000..a91ad3e --- /dev/null +++ b/src/lib/toast.service.ts @@ -0,0 +1,29 @@ +import toast from 'react-hot-toast'; +import axios from 'axios'; + +const success = (message: string) => + toast.success(message, { + style: { + background: '#22c55e', + color: '#fff', + fontWeight: '500', + }, + iconTheme: { primary: '#fff', secondary: '#22c55e' }, + }); + +const error = (error: unknown) => { + const msg = axios.isAxiosError(error) + ? error.response?.data?.message ?? 'Something went wrong' + : 'Something went wrong'; + + toast.error(msg, { + style: { + background: '#ef4444', + color: '#fff', + fontWeight: '500', + }, + iconTheme: { primary: '#fff', secondary: '#ef4444' }, + }); +}; + +export const toastService = { success, error }; \ No newline at end of file From 332220bab04ebbbcd1a30bfc4bf9035d063041bc Mon Sep 17 00:00:00 2001 From: Shoaib96978 Date: Sun, 14 Jun 2026 00:56:19 +0500 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20Case=20Management=20=E2=80=94=20Ref?= =?UTF-8?q?actor,=20Validation,=20Toast,=20Responsive=20&=20UX=20Improveme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/cases/api/case-api.ts | 13 +- .../components/case-drawer/CaseDrawer.tsx | 381 ------ .../case-list/CaseDrawer/CaseDrawer.tsx | 91 ++ .../CaseDrawer/shared/DrawerEmpty.tsx | 19 + .../CaseDrawer/shared/DrawerLoader.tsx | 8 + .../CaseDrawer/shared/case-drawer.helpers.ts | 30 + .../CaseDrawer/tabs/DocumentsTab.tsx | 112 ++ .../CaseDrawer/tabs/FollowUpsTab.tsx | 123 ++ .../cases/components/case-list/CaseList.tsx | 88 +- .../Casedetailsmodal.tsx} | 4 +- .../{ => sections}/CaseFilterTabs.tsx | 0 .../{ => sections}/CasePageHeader.tsx | 0 .../case-list/{ => sections}/CaseStatsBar.tsx | 23 +- .../case-list/{ => sections}/CaseTable.tsx | 2 +- .../create-case/CreateCaseModal.tsx | 560 ++------- .../sections/Caseparticularssection.tsx | 77 ++ .../create-case/sections/Datesection.tsx | 51 + .../create-case/sections/Dropdownfield.tsx | 82 ++ .../create-case/sections/Emailsection.tsx | 91 ++ .../sections/Jurisdictionsection.tsx | 84 ++ .../sections/Petitionersection.tsx | 87 ++ src/features/cases/hooks/useCases.ts | 2 +- .../cases/hooks/usecreatecasevalidation.ts | 66 + src/features/cases/styles/case-list.css | 336 ++++- src/features/cases/styles/create-case.css | 31 +- src/features/cases/types/case.types.ts | 12 +- src/features/documents/api/documents.ts | 2 +- src/features/documents/hooks/useDocuments.ts | 2 +- .../types/{index.ts => document.types.ts} | 0 .../followup/components/FollowUpList.tsx | 1096 ++++++++--------- .../followup/components/FollowUpsPage.tsx | 106 +- .../followup/components/FollowupForm.tsx | 467 ++----- src/features/followup/components/Fufield.tsx | 23 + .../components/utilies/followup.helpers.ts | 62 +- .../followup/styles/followUps-form.css | 124 ++ .../followup/styles/followup-list.css | 40 +- src/router/index.tsx | 6 +- src/shared/components/Layout.tsx | 424 +++---- 38 files changed, 2626 insertions(+), 2099 deletions(-) delete mode 100644 src/features/cases/components/case-drawer/CaseDrawer.tsx create mode 100644 src/features/cases/components/case-list/CaseDrawer/CaseDrawer.tsx create mode 100644 src/features/cases/components/case-list/CaseDrawer/shared/DrawerEmpty.tsx create mode 100644 src/features/cases/components/case-list/CaseDrawer/shared/DrawerLoader.tsx create mode 100644 src/features/cases/components/case-list/CaseDrawer/shared/case-drawer.helpers.ts create mode 100644 src/features/cases/components/case-list/CaseDrawer/tabs/DocumentsTab.tsx create mode 100644 src/features/cases/components/case-list/CaseDrawer/tabs/FollowUpsTab.tsx rename src/features/cases/components/{CaseDetailsModal.tsx => case-list/Casedetailsmodal.tsx} (98%) rename src/features/cases/components/case-list/{ => sections}/CaseFilterTabs.tsx (100%) rename src/features/cases/components/case-list/{ => sections}/CasePageHeader.tsx (100%) rename src/features/cases/components/case-list/{ => sections}/CaseStatsBar.tsx (62%) rename src/features/cases/components/case-list/{ => sections}/CaseTable.tsx (99%) create mode 100644 src/features/cases/components/create-case/sections/Caseparticularssection.tsx create mode 100644 src/features/cases/components/create-case/sections/Datesection.tsx create mode 100644 src/features/cases/components/create-case/sections/Dropdownfield.tsx create mode 100644 src/features/cases/components/create-case/sections/Emailsection.tsx create mode 100644 src/features/cases/components/create-case/sections/Jurisdictionsection.tsx create mode 100644 src/features/cases/components/create-case/sections/Petitionersection.tsx create mode 100644 src/features/cases/hooks/usecreatecasevalidation.ts rename src/features/documents/types/{index.ts => document.types.ts} (100%) create mode 100644 src/features/followup/components/Fufield.tsx diff --git a/src/features/cases/api/case-api.ts b/src/features/cases/api/case-api.ts index 8146d6a..99190a9 100644 --- a/src/features/cases/api/case-api.ts +++ b/src/features/cases/api/case-api.ts @@ -1,11 +1,12 @@ import AxiosInstance from "../../../lib/axios"; -import type { ApiResponse, PaginatedResponse } from "../../../shared/types/api.types"; -import type { CaseDto, CreateCaseDto, SearchCaseResponse, SearchParams } from "../types/case.types"; +import type { ApiResponse } from "../../../shared/types/api.types"; +import type { CreateCaseDto, SearchCaseResponse, SearchParams } from "../types/case.types"; +import type { PaginatedCaseResponse } from '../types/case.types'; // ── Helper ──────────────────────────────────────────────────────── const withTotalPages = ( - response: ApiResponse> -): ApiResponse> => { + response: ApiResponse +): ApiResponse => { if (!response.data) return response; return { @@ -24,8 +25,8 @@ const withTotalPages = ( export const getCases = async ( page: number = 1, pageSize: number = 10 -): Promise>> => { - const response = await AxiosInstance.get>>( +): Promise> => { + const response = await AxiosInstance.get>( '/Case/getAll', { params: { page, pageSize } } ); diff --git a/src/features/cases/components/case-drawer/CaseDrawer.tsx b/src/features/cases/components/case-drawer/CaseDrawer.tsx deleted file mode 100644 index b0d01b3..0000000 --- a/src/features/cases/components/case-drawer/CaseDrawer.tsx +++ /dev/null @@ -1,381 +0,0 @@ -import { useState, useEffect } from 'react' -import { useGetFollowUps, useDeleteFollowUp } from '../../../followup/hooks/useFollowups'; -// import { useCreateFollowUp, useUpdateFollowUp } from '../../../followup/hooks/useFollowups'; -import { useGetDocumentsByCase, useDeleteDocument } from '../../../documents/hooks/useDocuments' -import UploadDocumentModal from '../../../documents/components/uploadDocument' -import FollowUpForm from '../../../followup/components/FollowupForm' -import type { CaseDto } from '../../types/case.types' -import type { FollowUp } from '../../../followup/types/followup.types' -import '../../styles/case-drawer.css' - -// ── Types ───────────────────────────────────────────────────── - -type DrawerTab = 'followups' | 'documents' - -interface Props { - caseItem: CaseDto - defaultTab?: DrawerTab - onClose: () => void -} - -// ── Helpers ─────────────────────────────────────────────────── - -const fmtDate = (iso?: string | null) => - iso ? new Date(iso).toLocaleDateString('en-PK', { - day: '2-digit', month: 'short', year: 'numeric', - }) : '—' - -const getFileIcon = (fileType: string) => { - if (fileType.includes('pdf')) return '📄' - if (fileType.includes('image')) return '🖼️' - if (fileType.includes('word') || fileType.includes('doc')) return '📝' - return '📎' -} - -const formatFileSize = (bytes: number) => { - if (bytes < 1024) return `${bytes} B` - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` - return `${(bytes / (1024 * 1024)).toFixed(1)} MB` -} - -const getHearingStatus = (hearingDate: string, nextHearingDate?: string | null) => { - const today = new Date() - const hearing = new Date(hearingDate) - if (hearing < today) return { bg: '#FEF2F2', color: '#DC2626', border: '#FECACA', text: 'Past' } - if (nextHearingDate) return { bg: '#FEF3C7', color: '#D97706', border: '#FDE68A', text: 'Adjourned' } - return { bg: '#F0FDF4', color: '#15803D', border: '#BBF7D0', text: 'Scheduled' } -} - -// ── Main Component ──────────────────────────────────────────── - -export default function CaseDrawer({ caseItem, defaultTab = 'followups', onClose }: Props) { - const [activeTab, setActiveTab] = useState(defaultTab) - const [visible, setVisible] = useState(false) - - // Smooth open animation - useEffect(() => { - const t = setTimeout(() => setVisible(true), 10) - return () => clearTimeout(t) - }, []) - - const handleClose = () => { - setVisible(false) - setTimeout(onClose, 300) // animation complete hone do - } - - // ESC key se close - useEffect(() => { - const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') handleClose() } - window.addEventListener('keydown', handler) - return () => window.removeEventListener('keydown', handler) - }, []) - - return ( - <> - {/* Overlay — click karo to close */} -
- - {/* Drawer Panel */} -
- - {/* ── HEADER ── */} -
-
-
📋
-
-
{caseItem.title}
-
- {caseItem.caseNo} - · - {caseItem.courtName} -
-
-
- -
- - {/* ── TABS ── */} -
- - -
- - {/* ── CONTENT ── */} -
- {activeTab === 'followups' && ( - - )} - {activeTab === 'documents' && ( - - )} -
- -
- - ) -} - -// ── Follow-ups Tab ──────────────────────────────────────────── - -function FollowUpsTab({ caseId }: { caseId: string }) { - const [showForm, setShowForm] = useState(false) - const [selected, setSelected] = useState() - - const { data, isLoading, isError, refetch } = useGetFollowUps({ pageNumber: 1, pageSize: 50, caseId }) - const { mutate: deleteFollowUp, isPending: isDeleting } = useDeleteFollowUp() - - const followups = data?.items ?? [] - - const handleEdit = (f: FollowUp) => { - setSelected(f) - setShowForm(true) - } - - const handleClose = () => { - setSelected(undefined) - setShowForm(false) - } - - if (isLoading) return - - if (isError) return ( -
- ⚠️ Could not load follow-ups. - -
- ) - - return ( -
- - {/* Add button */} -
- {followups.length} follow-up{followups.length !== 1 ? 's' : ''} - -
- - {/* List */} - {followups.length === 0 ? ( - setShowForm(true)} - /> - ) : ( -
- {followups.map((f, i) => { - const st = getHearingStatus(f.hearingDate, f.nextHearingDate) - return ( -
-
- {fmtDate(f.hearingDate)} - {st.text} -
- - {f.nextHearingDate && ( -
- Next Hearing - {fmtDate(f.nextHearingDate)} -
- )} - {f.interimOrder && ( -
- Interim Order - {f.interimOrder} -
- )} - {f.decision && ( -
- Decision - {f.decision} -
- )} - {f.remarks && ( -
- Remarks - {f.remarks} -
- )} - -
- - -
-
- ) - })} -
- )} - - {/* FollowUp Form Modal */} - {showForm && ( - - )} -
- ) -} - -// ── Documents Tab ───────────────────────────────────────────── - -function DocumentsTab({ caseId }: { caseId: string }) { - const [showUpload, setShowUpload] = useState(false) - const [search, setSearch] = useState('') - - const { data: documents, isLoading, isError, refetch } = useGetDocumentsByCase(caseId) - const { mutate: deleteDoc, isPending: isDeleting } = useDeleteDocument(caseId) - - const filtered = (documents ?? []).filter(d => - d.fileName.toLowerCase().includes(search.toLowerCase()) || - (d.remarks ?? '').toLowerCase().includes(search.toLowerCase()) - ) - - if (isLoading) return - - if (isError) return ( -
- ⚠️ Could not load documents. - -
- ) - - return ( -
- - {/* Toolbar */} -
- {(documents ?? []).length} document{(documents ?? []).length !== 1 ? 's' : ''} - -
- - {/* Search */} - {(documents ?? []).length > 0 && ( -
- - setSearch(e.target.value)} - placeholder="Search documents..." - /> -
- )} - - {/* List */} - {filtered.length === 0 ? ( - setShowUpload(true)} - showBtn={(documents ?? []).length === 0} - /> - ) : ( -
- - {filtered.map((doc, i) => ( -
-
{getFileIcon(doc.fileType)}
-
-
{doc.fileName}
-
- {formatFileSize(doc.fileSize)} - · - {fmtDate(doc.createdAt)} -
- {doc.remarks && ( -
{doc.remarks}
- )} -
-
- - - - -
-
- ))} -
- )} - - {/* Upload Modal */} - {showUpload && ( - setShowUpload(false)} - /> - )} -
- ) -} - -// ── Small Helpers ───────────────────────────────────────────── - -function DrawerLoader({ text }: { text: string }) { - return ( -
-
-

{text}

-
- ) -} - -function DrawerEmpty({ icon, text, btnText, onAdd, showBtn = true }: { - icon: string; text: string; btnText: string; onAdd: () => void; showBtn?: boolean -}) { - return ( -
-
{icon}
-

{text}

- {showBtn && ( - - )} -
- ) -} \ No newline at end of file diff --git a/src/features/cases/components/case-list/CaseDrawer/CaseDrawer.tsx b/src/features/cases/components/case-list/CaseDrawer/CaseDrawer.tsx new file mode 100644 index 0000000..e892a18 --- /dev/null +++ b/src/features/cases/components/case-list/CaseDrawer/CaseDrawer.tsx @@ -0,0 +1,91 @@ +import { useState, useEffect } from 'react' +import type { CaseDto } from '../../../types/case.types' +import FollowUpsTab from './tabs/FollowUpsTab' +import DocumentsTab from './tabs/DocumentsTab' +import '../../../styles/case-drawer.css' + +type DrawerTab = 'followups' | 'documents' + +interface Props { + caseItem: CaseDto + defaultTab?: DrawerTab + onClose: () => void +} + +export default function CaseDrawer({ caseItem, defaultTab = 'followups', onClose }: Props) { + const [activeTab, setActiveTab] = useState(defaultTab) + const [visible, setVisible] = useState(false) + + // Smooth open animation + useEffect(() => { + const t = setTimeout(() => setVisible(true), 10) + return () => clearTimeout(t) + }, []) + + const handleClose = () => { + setVisible(false) + setTimeout(onClose, 300) + } + + // ESC key se close + useEffect(() => { + const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') handleClose() } + window.addEventListener('keydown', handler) + return () => window.removeEventListener('keydown', handler) + }, []) + + return ( + <> + {/* Overlay */} +
+ + {/* Drawer Panel */} +
+ + {/* HEADER */} +
+
+
📋
+
+
{caseItem.title}
+
+ {caseItem.caseNo} + · + {caseItem.courtName} +
+
+
+ +
+ + {/* TABS */} +
+ + +
+ + {/* CONTENT */} +
+ {activeTab === 'followups' && } + {activeTab === 'documents' && } +
+ +
+ + ) +} \ No newline at end of file diff --git a/src/features/cases/components/case-list/CaseDrawer/shared/DrawerEmpty.tsx b/src/features/cases/components/case-list/CaseDrawer/shared/DrawerEmpty.tsx new file mode 100644 index 0000000..3fff507 --- /dev/null +++ b/src/features/cases/components/case-list/CaseDrawer/shared/DrawerEmpty.tsx @@ -0,0 +1,19 @@ +interface DrawerEmptyProps { + icon: string + text: string + btnText: string + onAdd: () => void + showBtn?: boolean +} + +export default function DrawerEmpty({ icon, text, btnText, onAdd, showBtn = true }: DrawerEmptyProps) { + return ( +
+
{icon}
+

{text}

+ {showBtn && ( + + )} +
+ ) +} \ No newline at end of file diff --git a/src/features/cases/components/case-list/CaseDrawer/shared/DrawerLoader.tsx b/src/features/cases/components/case-list/CaseDrawer/shared/DrawerLoader.tsx new file mode 100644 index 0000000..6ca490e --- /dev/null +++ b/src/features/cases/components/case-list/CaseDrawer/shared/DrawerLoader.tsx @@ -0,0 +1,8 @@ +export default function DrawerLoader({ text }: { text: string }) { + return ( +
+
+

{text}

+
+ ) +} \ No newline at end of file diff --git a/src/features/cases/components/case-list/CaseDrawer/shared/case-drawer.helpers.ts b/src/features/cases/components/case-list/CaseDrawer/shared/case-drawer.helpers.ts new file mode 100644 index 0000000..8977e4a --- /dev/null +++ b/src/features/cases/components/case-list/CaseDrawer/shared/case-drawer.helpers.ts @@ -0,0 +1,30 @@ +// ── Shared helpers for CaseDrawer tabs ─────────────────────── + +export const fmtDate = (iso?: string | null) => + iso + ? new Date(iso).toLocaleDateString('en-PK', { + day: '2-digit', month: 'short', year: 'numeric', + }) + : '—' + +export const getHearingStatus = (hearingDate: string, nextHearingDate?: string | null) => { + const today = new Date() + const hearing = new Date(hearingDate) + if (hearing < today) return { bg: '#FEF2F2', color: '#DC2626', border: '#FECACA', text: 'Past' } + if (nextHearingDate) return { bg: '#FEF3C7', color: '#D97706', border: '#FDE68A', text: 'Adjourned' } + return { bg: '#F0FDF4', color: '#15803D', border: '#BBF7D0', text: 'Scheduled' } +} + +export const getFileIcon = (fileType: string) => { + if (fileType.includes('pdf')) return '📄' + if (fileType.includes('image')) return '🖼️' + if (fileType.includes('word') || fileType.includes('doc')) return '📝' + if (fileType.includes('excel') || fileType.includes('sheet')) return '📊' + return '📎' +} + +export const formatFileSize = (bytes: number) => { + if (bytes < 1024) return `${bytes} B` + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` + return `${(bytes / (1024 * 1024)).toFixed(1)} MB` +} \ No newline at end of file diff --git a/src/features/cases/components/case-list/CaseDrawer/tabs/DocumentsTab.tsx b/src/features/cases/components/case-list/CaseDrawer/tabs/DocumentsTab.tsx new file mode 100644 index 0000000..28be6ff --- /dev/null +++ b/src/features/cases/components/case-list/CaseDrawer/tabs/DocumentsTab.tsx @@ -0,0 +1,112 @@ +import { useState } from 'react' +import { useGetDocumentsByCase, useDeleteDocument } from '../../../../../documents/hooks/useDocuments' +import UploadDocumentModal from '../../../../../documents/components/uploadDocument' +import DrawerLoader from '../shared/DrawerLoader' +import DrawerEmpty from '../shared/DrawerEmpty' +import { fmtDate, getFileIcon, formatFileSize } from '../shared/case-drawer.helpers' + +export default function DocumentsTab({ caseId }: { caseId: string }) { + const [showUpload, setShowUpload] = useState(false) + const [search, setSearch] = useState('') + + const { data: documents, isLoading, isError, refetch } = useGetDocumentsByCase(caseId) + const { mutate: deleteDoc, isPending: isDeleting } = useDeleteDocument(caseId) + + const filtered = (documents ?? []).filter(d => + d.fileName.toLowerCase().includes(search.toLowerCase()) || + (d.remarks ?? '').toLowerCase().includes(search.toLowerCase()) + ) + + if (isLoading) return + + if (isError) return ( +
+ ⚠️ Could not load documents. + +
+ ) + + return ( +
+ + {/* Toolbar */} +
+ + {(documents ?? []).length} document{(documents ?? []).length !== 1 ? 's' : ''} + + +
+ + {/* Search */} + {(documents ?? []).length > 0 && ( +
+ + setSearch(e.target.value)} + placeholder="Search documents..." + /> +
+ )} + + {/* List */} + {filtered.length === 0 ? ( + setShowUpload(true)} + showBtn={(documents ?? []).length === 0} + /> + ) : ( +
+ {filtered.map((doc, i) => ( +
+
{getFileIcon(doc.fileType)}
+
+
{doc.fileName}
+
+ {formatFileSize(doc.fileSize)} + · + {fmtDate(doc.createdAt)} +
+ {doc.remarks && ( +
{doc.remarks}
+ )} +
+
+ + + + +
+
+ ))} +
+ )} + + {showUpload && ( + setShowUpload(false)} + /> + )} +
+ ) +} \ No newline at end of file diff --git a/src/features/cases/components/case-list/CaseDrawer/tabs/FollowUpsTab.tsx b/src/features/cases/components/case-list/CaseDrawer/tabs/FollowUpsTab.tsx new file mode 100644 index 0000000..c9c1fdd --- /dev/null +++ b/src/features/cases/components/case-list/CaseDrawer/tabs/FollowUpsTab.tsx @@ -0,0 +1,123 @@ +import { useState } from 'react' +import { useGetFollowUps, useDeleteFollowUp } from '../../../../../followup/hooks/useFollowups' +import FollowUpForm from '../../../../../followup/components/FollowupForm' +import type { FollowUp } from '../../../../../followup/types/followup.types' +import DrawerLoader from '../shared/DrawerLoader' +import DrawerEmpty from '../shared/DrawerEmpty' +import { fmtDate, getHearingStatus } from '../shared/case-drawer.helpers' + +export default function FollowUpsTab({ caseId }: { caseId: string }) { + const [showForm, setShowForm] = useState(false) + const [selected, setSelected] = useState() + + const { data, isLoading, isError, refetch } = useGetFollowUps({ pageNumber: 1, pageSize: 50, caseId }) + const { mutate: deleteFollowUp, isPending: isDeleting } = useDeleteFollowUp() + + const followups = data?.items ?? [] + + const handleEdit = (f: FollowUp) => { + setSelected(f) + setShowForm(true) + } + + const handleClose = () => { + setSelected(undefined) + setShowForm(false) + } + + if (isLoading) return + + if (isError) return ( +
+ ⚠️ Could not load follow-ups. + +
+ ) + + return ( +
+ + {/* Toolbar */} +
+ + {followups.length} follow-up{followups.length !== 1 ? 's' : ''} + + +
+ + {/* List */} + {followups.length === 0 ? ( + setShowForm(true)} + /> + ) : ( +
+ {followups.map((f, i) => { + const st = getHearingStatus(f.hearingDate, f.nextHearingDate) + return ( +
+
+ {fmtDate(f.hearingDate)} + {st.text} +
+ + {f.nextHearingDate && ( +
+ Next Hearing + {fmtDate(f.nextHearingDate)} +
+ )} + {f.interimOrder && ( +
+ Interim Order + {f.interimOrder} +
+ )} + {f.decision && ( +
+ Decision + {f.decision} +
+ )} + {f.remarks && ( +
+ Remarks + {f.remarks} +
+ )} + +
+ + +
+
+ ) + })} +
+ )} + + {showForm && ( + + )} +
+ ) +} \ No newline at end of file diff --git a/src/features/cases/components/case-list/CaseList.tsx b/src/features/cases/components/case-list/CaseList.tsx index 88ec4af..ace8e73 100644 --- a/src/features/cases/components/case-list/CaseList.tsx +++ b/src/features/cases/components/case-list/CaseList.tsx @@ -1,12 +1,12 @@ import { useState, useCallback } from 'react'; -import CasePageHeader from './CasePageHeader'; -import CaseStatsBar from './CaseStatsBar'; -import CaseFilterTabs from './CaseFilterTabs'; -import type { CaseFilterTab } from './CaseFilterTabs'; -import CaseTable from './CaseTable'; -import CaseDrawer from '../case-drawer/CaseDrawer'; +import CasePageHeader from './sections/CasePageHeader'; +import CaseStatsBar from './sections/CaseStatsBar'; +import CaseFilterTabs from './sections/CaseFilterTabs'; +import type { CaseFilterTab } from './sections/CaseFilterTabs'; +import CaseTable from './sections/CaseTable'; +import CaseDrawer from './CaseDrawer/CaseDrawer'; import CreateCaseModal from '../create-case/CreateCaseModal'; -import CaseDetailsModal from '../CaseDetailsModal'; +import CaseDetailsModal from './Casedetailsmodal'; import { useGetAllCases, useSearchCases, useDeleteCase } from '../../hooks/useCases'; import { useCaseModal } from '../../hooks/useCaseModal'; import type { CaseDto, SearchParams } from '../../types/case.types'; // ✅ CaseDto only, no GetCaseDto @@ -25,6 +25,7 @@ export default function CasesList() { // ── Pagination & tab state ──────────────────────────────── const [currentPage, setCurrentPage] = useState(1); const [activeTab, setActiveTab] = useState('All'); + const [statsCollapsed, setStatsCollapsed] = useState(false) // ── Search field state ──────────────────────────────────── // Single query field — user types here, hits Search, triggers API call @@ -38,7 +39,7 @@ export default function CasesList() { const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [selectedCaseId, setSelectedCaseId] = useState(null); - const pageSize = 10; + const [pageSize, setPageSize] = useState(5) const deleteCaseMutation = useDeleteCase(); // ── Data fetching ───────────────────────────────────────── @@ -47,6 +48,7 @@ export default function CasesList() { data: listResponse, isLoading: listLoading, isError: listError, + refetch: refetchList, } = useGetAllCases(currentPage, pageSize); // ✅ renamed from HandleGetAllCases const { @@ -54,6 +56,7 @@ export default function CasesList() { isLoading: searchLoading, isError: searchError, isFetching: searchFetching, + refetch: refetchSearch, } = useSearchCases({ // ✅ server-side filter — no more client filter ...activeSearch, status: activeTab !== 'All' ? activeTab : undefined, // tab drives status filter @@ -69,6 +72,8 @@ export default function CasesList() { const cases = (response?.data?.items ?? []) as CaseDto[]; const totalCasesCount = response?.data?.totalCount ?? 0; + const pendingCount = listResponse?.data?.pendingCount ?? 0; + const finalizedCount = listResponse?.data?.finalizedCount ?? 0; const totalPages = response?.data?.totalPages ?? 0; // ── Modal helpers ───────────────────────────────────────── @@ -139,11 +144,16 @@ export default function CasesList() { setDrawer({ open: true, caseItem, tab: 'followups' }); }; + const handleRefresh = () => { + if (isSearching || activeTab !== 'All') refetchSearch?.() + else refetchList() + } + // ---- Active Tab ----------------- const counts: Record = { All: totalCasesCount, - Pending: cases.filter(c => c.status === 'Pending').length, - Finalized: cases.filter(c => c.status === 'Finalized').length, + Pending: listResponse?.data?.pendingCount ?? 0, + Finalized: listResponse?.data?.finalizedCount ?? 0, }; // ── Render ──────────────────────────────────────────────── @@ -156,8 +166,25 @@ export default function CasesList() {
+ {/* Stats header with collapse btn */} +
+ +
+ {/* ── STATS ── */} - + {!statsCollapsed && ( + )} {/* ── FILTER TABS ── */} @@ -227,13 +254,38 @@ export default function CasesList() { <>
📋 Case Registry - - {isSearching - ? `${totalCasesCount} result${totalCasesCount !== 1 ? 's' : ''}` - : activeTab === 'All' - ? `${totalCasesCount} cases` - : `${totalCasesCount} ${activeTab}`} - + +
+ {/* Page size selector */} + + + {/* Refresh btn */} + + + + {isSearching + ? `${totalCasesCount} result${totalCasesCount !== 1 ? 's' : ''}` + : `${totalCasesCount} cases`} + +
c.status === 'Pending').length; - const finalizedCount = cases.filter(c => c.status === 'Finalized').length; +export default function CaseStatsBar({ totalCount, pendingCount, finalizedCount }: Props) { return (
@@ -21,23 +18,19 @@ export default function CaseStatsBar({ totalCount, cases }: Props) {
- - {pendingCount} - + {pendingCount} Pending
-

Active Cases

+

Pending Cases

- - {finalizedCount} - + {finalizedCount} Finalized
-

Completed

+

Completed Cases

- ); + ) } diff --git a/src/features/cases/components/case-list/CaseTable.tsx b/src/features/cases/components/case-list/sections/CaseTable.tsx similarity index 99% rename from src/features/cases/components/case-list/CaseTable.tsx rename to src/features/cases/components/case-list/sections/CaseTable.tsx index 843244f..b81060c 100644 --- a/src/features/cases/components/case-list/CaseTable.tsx +++ b/src/features/cases/components/case-list/sections/CaseTable.tsx @@ -1,4 +1,4 @@ -import type { CaseDto, CaseStatus } from '../../types/case.types'; +import type { CaseDto, CaseStatus } from '../../../types/case.types'; // ── Types ───────────────────────────────────────────────────────── diff --git a/src/features/cases/components/create-case/CreateCaseModal.tsx b/src/features/cases/components/create-case/CreateCaseModal.tsx index 23c6e53..a34a942 100644 --- a/src/features/cases/components/create-case/CreateCaseModal.tsx +++ b/src/features/cases/components/create-case/CreateCaseModal.tsx @@ -1,16 +1,21 @@ import { useState } from 'react'; -import type { KeyboardEvent } from 'react'; import '../../styles/case-list.css'; import '../../styles/create-case.css'; import { useCreateCase, useDropDownPetitioners, useDropDownDepartments, useDropDownCourts } from '../../hooks/useCases'; +import { validateCaseForm, hasErrors, type CaseFormErrors } from '../../hooks/Usecreatecasevalidation'; +import type { CreateCaseDto } from '../../types/case.types'; import type { PetitionerDto } from '../../../petitioners/types/petitioner.types'; import type { Department } from '../../../departments/types/department.types'; import type { Court } from '../../../courts/types/court.types'; -import type { CreateCaseDto } from "../../types/case.types"; -const toUtcInstitutionDate = (date: string) => { - return new Date(`${date}T00:00:00.000Z`).toISOString(); -}; +import JurisdictionSection from './sections/Jurisdictionsection'; +import PetitionerSection from './sections/Petitionersection'; +import CaseParticularsSection from './sections/Caseparticularssection'; +import DateSection from './sections/Datesection'; +import EmailSection from './sections/Emailsection'; + +const toUtcInstitutionDate = (date: string) => + new Date(`${date}T00:00:00.000Z`).toISOString(); interface CreateCaseModalProps { onClose: () => void; @@ -18,205 +23,82 @@ interface CreateCaseModalProps { export default function CreateCaseModal({ onClose }: CreateCaseModalProps) { - // ── Date ──────────────────────────────────────────────── - const [selectedDate, setSelectedDate] = useState(''); - - // ── Petitioner ────────────────────────────────────────── + // ── Dropdown enable state ──────────────────────────────── const [petitionerEnabled, setPetitionerEnabled] = useState(false); - const [selectedPetitionerId, setSelectedPetitionerId] = useState(''); - - // ── Department ────────────────────────────────────────── const [deptEnabled, setDeptEnabled] = useState(false); - - // ── Court ─────────────────────────────────────────────── const [courtEnabled, setCourtEnabled] = useState(false); - // ── Email tags ────────────────────────────────────────── - const [emailInput, setEmailInput] = useState(''); - const [emailTags, setEmailTags] = useState([]); - - // Form States + // ── Form state ─────────────────────────────────────────── const [courtId, setCourtId] = useState(''); const [deptId, setDeptId] = useState(''); + const [selectedPetitionerId, setSelectedPetitionerId] = useState(''); const [dag, setDag] = useState(''); const [title, setTitle] = useState(''); const [subject, setSubject] = useState(''); const [detail, setDetail] = useState(''); + const [selectedDate, setSelectedDate] = useState(''); + const [emailInput, setEmailInput] = useState(''); + const [emailTags, setEmailTags] = useState([]); - // ── Hooks ─────────────────────────────────────────────── - const { - data: petData, - isLoading: petLoading, - isError: petError, - refetch: refetchPetitioners, - } = useDropDownPetitioners(petitionerEnabled); - - const { - data: deptData, - isLoading: deptLoading, - isError: deptError, - refetch: refetchDepts, - } = useDropDownDepartments(deptEnabled); - - const { - data: courtData, - isLoading: courtLoading, - isError: courtError, - refetch: refetchCourts, - } = useDropDownCourts(courtEnabled); - - const { mutate: createCase, isPending, isError: submitError } = useCreateCase() + // ── Validation state ───────────────────────────────────── + const [errors, setErrors] = useState({}); - // ───────────────────────────────────────────────────────────── - // SAFE DROPDOWN DATA - // ───────────────────────────────────────────────────────────── + // ── Hooks ──────────────────────────────────────────────── + const { data: petData, isLoading: petLoading, isError: petError, refetch: refetchPetitioners } = useDropDownPetitioners(petitionerEnabled); + const { data: deptData, isLoading: deptLoading, isError: deptError, refetch: refetchDepts } = useDropDownDepartments(deptEnabled); + const { data: courtData, isLoading: courtLoading, isError: courtError, refetch: refetchCourts } = useDropDownCourts(courtEnabled); + const { mutate: createCase, isPending } = useCreateCase(); const petitioners: PetitionerDto[] = petData?.data ?? []; - - // Departments const departments: Department[] = deptData?.items ?? []; - - // Courts const courts: Court[] = courtData?.items ?? []; - - // ── Selected petitioner → info pill ───────────────────── - const selected = petitioners.find((p) => p.id === selectedPetitionerId); - const cnicDisplay = selected?.cnic ?? '—'; - const emailDisplay = selected?.email ?? '—'; - - // ── Email tag handlers ─────────────────────────────────── - const isValidEmail = (val: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val.trim()); - - const addEmailTag = () => { - const trimmed = emailInput.trim(); - if (!trimmed) return; - if (!isValidEmail(trimmed)) return; - if (emailTags.includes(trimmed)) return; // duplicate nahi - setEmailTags(prev => [...prev, trimmed]); - setEmailInput(''); + // ── Clear single field error when user fixes it ────────── + const clearError = (field: keyof CaseFormErrors) => { + setErrors(prev => { + const next = { ...prev }; + delete next[field]; + return next; + }); }; - const handleEmailKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter' || e.key === ',') { - e.preventDefault(); - addEmailTag(); - } - // Backspace se last tag remove - if (e.key === 'Backspace' && emailInput === '' && emailTags.length > 0) { - setEmailTags(prev => prev.slice(0, -1)); - } - }; - - const removeEmailTag = (tag: string) => { - setEmailTags(prev => prev.filter((t) => t !== tag)); - }; - - // ── Refresh handlers — enabled true karo + refetch ────── - const handleRefreshPetitioners = () => { - setPetitionerEnabled(true); - refetchPetitioners(); - }; - const handleRefreshDepts = () => { - setDeptEnabled(true); - refetchDepts(); - }; - const handleRefreshCourts = () => { - setCourtEnabled(true); - refetchCourts(); + // ── Clear all ──────────────────────────────────────────── + const handleClear = () => { + setCourtId(''); setDeptId(''); setSelectedPetitionerId(''); + setDag(''); setTitle(''); setSubject(''); setDetail(''); + setSelectedDate(''); setEmailTags([]); setEmailInput(''); + setErrors({}); }; - // submit handler + // ── Submit ─────────────────────────────────────────────── const handleSubmit = () => { - // simple required check - if (!courtId || !deptId || !selectedPetitionerId || !dag || !title || !subject || !detail || !selectedDate) { - alert('Please fill all required fields') - return + const validationErrors = validateCaseForm({ + courtId, deptId, selectedPetitionerId, + dag, title, subject, detail, selectedDate, + }); + + if (hasErrors(validationErrors)) { + setErrors(validationErrors); + return; } + setErrors({}); + const payload: CreateCaseDto = { courtId, departmentId: deptId, petitionerId: selectedPetitionerId, - dag, - title, - subject, - detail, + dag, title, subject, detail, dateInstitution: toUtcInstitutionDate(selectedDate), - emailList: emailTags.join(','), // ["a@b.com","c@d.com"] → "a@b.com,c@d.com" - } + emailList: emailTags.join(','), + }; createCase(payload, { onSuccess: (res) => { - if (res.isSuccess) { - onClose() // modal band karo - } - } - }) - } - - // ── Reusable dropdown renderer ─────────────────────────── - const renderDropdown = ( - id: string, - value: string, - onChange: (v: string) => void, - onFocusCb: () => void, - onRefresh: () => void, - isLoading: boolean, - isError: boolean, - enabled: boolean, - items: { id: string; label: string }[], - placeholder: string, - emptyMsg: string, - errorMsg: string, - ) => ( -
- - - {/* Refresh icon — select ke saath */} - - - {/* Error message neeche */} - {isError && !isLoading && ( -
- - {errorMsg} -
- )} -
- ); + if (res.isSuccess) onClose(); + }, + }); + }; return (
@@ -226,7 +108,7 @@ export default function CreateCaseModal({ onClose }: CreateCaseModalProps) {
- +
File New Case
@@ -235,295 +117,73 @@ export default function CreateCaseModal({ onClose }: CreateCaseModalProps) {
-
{/* BODY */}
- {/* SECTION I — JURISDICTION & ASSIGNMENT */} -
-
-
I
-
JURISDICTION & ASSIGNMENT
-
-
-
- - {/* Court */} -
- - {renderDropdown( - 'court', - courtId, - setCourtId, - () => setCourtEnabled(true), - handleRefreshCourts, - courtLoading, - courtError, - courtEnabled, - courts.map(c => ({ id: c.id, label: c.courtName })), - '— Select Court —', - 'No courts found', - 'Courts load nahi ho sake', - )} -
- - {/* Department */} -
- - {renderDropdown( - 'department', - deptId, - setDeptId, - () => setDeptEnabled(true), - handleRefreshDepts, - deptLoading, - deptError, - deptEnabled, - departments.map(d => ({ id: d.id, label: d.departmentName })), - '— Select Department —', - 'No departments found', - 'Departments load nahi ho sake', - )} -
- -
-
-
- - {/* SECTION II — PETITIONER & COUNSEL */} -
-
-
II
-
PETITIONER & COUNSEL
-
-
-
- - {/* Petitioner */} -
- - {renderDropdown( - 'petitioner', - selectedPetitionerId, - setSelectedPetitionerId, - () => setPetitionerEnabled(true), - handleRefreshPetitioners, - petLoading, - petError, - petitionerEnabled, - petitioners.map(p => ({ id: p.id, label: p.name })), - '— Select Petitioner —', - 'Petitioner list is currently empty', - 'Petitioners load nahi ho sake', - )} - - {/* Info pill */} -
- - - CNIC: {cnicDisplay} - - · - - - Email: {emailDisplay} - -
-
- - {/* DAG */} -
- - setDag(e.target.value)} - /> - - Deputy Attorney General or opposing counsel name - -
- -
-
-
- - {/* SECTION III — CASE PARTICULARS */} -
-
-
III
-
CASE PARTICULARS
-
-
-
- - setTitle(e.target.value)} - /> -
-
- - setSubject(e.target.value)} - /> -
-
- -