From f82664e30fde44350733aa164b2dab93f4e20ff2 Mon Sep 17 00:00:00 2001 From: Kasun Vithanage Date: Mon, 19 Jan 2026 22:16:57 +0530 Subject: [PATCH] feat: add inline editing for surgery view page Transform the surgery view page into an editable document where staff can quickly update any field before printing. Key changes: - Add InlineEditableField base component with keyboard shortcuts - Add InlineEditableRichText for rich text fields (notes, post-op, etc.) - Add InlineEditableText for simple text fields (BHT, ward) - Add InlineEditableDate for date picker fields - Add InlineEditableDoctors for doctor multi-select with search - Add EditableFieldCard wrapper component - All fields are now always visible with click-to-add placeholders - Each field saves independently with toast notifications - Rename "Edit" button to "Edit All" for bulk editing - Display notes cards as full-width rows for easier editing --- .../surgery/inline-edit/EditableFieldCard.tsx | 50 +++ .../inline-edit/InlineEditableDate.tsx | 101 +++++ .../inline-edit/InlineEditableDoctors.tsx | 273 ++++++++++++ .../inline-edit/InlineEditableField.tsx | 177 ++++++++ .../inline-edit/InlineEditableRichText.tsx | 90 ++++ .../inline-edit/InlineEditableText.tsx | 144 +++++++ .../components/surgery/inline-edit/index.ts | 17 + .../src/routes/surgeries/view-surgery.tsx | 388 ++++++++++-------- 8 files changed, 1069 insertions(+), 171 deletions(-) create mode 100644 src/renderer/src/components/surgery/inline-edit/EditableFieldCard.tsx create mode 100644 src/renderer/src/components/surgery/inline-edit/InlineEditableDate.tsx create mode 100644 src/renderer/src/components/surgery/inline-edit/InlineEditableDoctors.tsx create mode 100644 src/renderer/src/components/surgery/inline-edit/InlineEditableField.tsx create mode 100644 src/renderer/src/components/surgery/inline-edit/InlineEditableRichText.tsx create mode 100644 src/renderer/src/components/surgery/inline-edit/InlineEditableText.tsx create mode 100644 src/renderer/src/components/surgery/inline-edit/index.ts diff --git a/src/renderer/src/components/surgery/inline-edit/EditableFieldCard.tsx b/src/renderer/src/components/surgery/inline-edit/EditableFieldCard.tsx new file mode 100644 index 0000000..13633a6 --- /dev/null +++ b/src/renderer/src/components/surgery/inline-edit/EditableFieldCard.tsx @@ -0,0 +1,50 @@ +import { cn } from '@renderer/lib/utils' +import { Card, CardContent, CardHeader, CardTitle } from '@renderer/components/ui/card' +import { LucideIcon } from 'lucide-react' +import { ReactNode } from 'react' + +export interface EditableFieldCardProps { + /** Card title */ + title: string + /** Icon component */ + icon: LucideIcon + /** Icon background color class */ + iconBgColor: string + /** Icon color class */ + iconColor: string + /** Card content */ + children: ReactNode + /** Additional class name for the card */ + className?: string + /** Animation delay for entrance animation */ + animationDelay?: number +} + +export const EditableFieldCard = ({ + title, + icon: Icon, + iconBgColor, + iconColor, + children, + className, + animationDelay = 0 +}: EditableFieldCardProps) => { + return ( + + +
+
+ +
+ + {title} + +
+
+ {children} +
+ ) +} diff --git a/src/renderer/src/components/surgery/inline-edit/InlineEditableDate.tsx b/src/renderer/src/components/surgery/inline-edit/InlineEditableDate.tsx new file mode 100644 index 0000000..18e88a1 --- /dev/null +++ b/src/renderer/src/components/surgery/inline-edit/InlineEditableDate.tsx @@ -0,0 +1,101 @@ +import { cn, formatDate } from '@renderer/lib/utils' +import { Button } from '@renderer/components/ui/button' +import { Calendar } from '@renderer/components/ui/calendar' +import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover' +import { Pencil, X } from 'lucide-react' +import { useState, useCallback } from 'react' + +export interface InlineEditableDateProps { + /** Current date value */ + value: Date | null | undefined + /** Called when date is saved */ + onSave: (value: Date | null) => Promise + /** Placeholder for empty state */ + emptyPlaceholder?: string + /** Additional class name */ + className?: string +} + +export const InlineEditableDate = ({ + value, + onSave, + emptyPlaceholder = 'Select date...', + className +}: InlineEditableDateProps) => { + const [isOpen, setIsOpen] = useState(false) + const [isSaving, setIsSaving] = useState(false) + + const isEmpty = !value + + const handleSelect = useCallback( + async (date: Date | undefined) => { + setIsSaving(true) + try { + await onSave(date || null) + setIsOpen(false) + } finally { + setIsSaving(false) + } + }, + [onSave] + ) + + const handleClear = useCallback( + async (e: React.MouseEvent) => { + e.stopPropagation() + setIsSaving(true) + try { + await onSave(null) + setIsOpen(false) + } finally { + setIsSaving(false) + } + }, + [onSave] + ) + + return ( + + +
+ + {isEmpty ? emptyPlaceholder : formatDate(value)} + + +
+
+ + + {value && ( +
+ +
+ )} +
+
+ ) +} diff --git a/src/renderer/src/components/surgery/inline-edit/InlineEditableDoctors.tsx b/src/renderer/src/components/surgery/inline-edit/InlineEditableDoctors.tsx new file mode 100644 index 0000000..b69eb26 --- /dev/null +++ b/src/renderer/src/components/surgery/inline-edit/InlineEditableDoctors.tsx @@ -0,0 +1,273 @@ +import { cn } from '@renderer/lib/utils' +import { Button } from '@renderer/components/ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover' +import { Check, Pencil, Save, X, Search, UserPlus } from 'lucide-react' +import { useState, useCallback, useMemo, useRef } from 'react' +import { DoctorModel } from '@shared/models/DoctorModel' +import { useQuery } from '@tanstack/react-query' +import { queries } from '@renderer/lib/queries' +import { Input } from '@renderer/components/ui/input' +import { Checkbox } from '@renderer/components/ui/checkbox' +import { ScrollArea } from '@renderer/components/ui/scroll-area' +import { Link } from 'react-router-dom' +import { + Sheet, + SheetContent, + SheetFooter, + SheetHeader, + SheetTitle +} from '@renderer/components/ui/sheet' +import { AddOrEditDoctor, AddOrEditDoctorRef } from '@renderer/components/doctor/AddOrEditDoctor' + +export interface InlineEditableDoctorsProps { + /** Currently assigned doctors */ + doctors: DoctorModel[] | undefined + /** Called when doctors are saved */ + onSave: (doctorIds: number[]) => Promise + /** Placeholder for empty state */ + emptyPlaceholder?: string + /** Label for the doctor type (e.g., "surgeons", "assistants") */ + doctorTypeLabel?: string + /** Additional class name */ + className?: string +} + +export const InlineEditableDoctors = ({ + doctors, + onSave, + emptyPlaceholder = 'No doctors assigned', + doctorTypeLabel = 'doctors', + className +}: InlineEditableDoctorsProps) => { + const [isOpen, setIsOpen] = useState(false) + const [isSaving, setIsSaving] = useState(false) + const [search, setSearch] = useState('') + const [selectedIds, setSelectedIds] = useState([]) + const [addNewSheetOpen, setAddNewSheetOpen] = useState(false) + const addDoctorFormRef = useRef(null) + + const isEmpty = !doctors || doctors.length === 0 + + // Fetch all doctors for selection + const { data: doctorList, refetch } = useQuery({ + ...queries.doctors.list({ pageSize: 100, search }) + }) + + // Filter doctors based on search + const filteredDoctors = useMemo(() => { + const doctors = doctorList?.data || [] + if (!search.trim()) return doctors + const searchLower = search.toLowerCase() + return doctors.filter( + (d) => + d.name.toLowerCase().includes(searchLower) || + d.designation?.toLowerCase().includes(searchLower) + ) + }, [doctorList?.data, search]) + + const handleOpen = useCallback( + (open: boolean) => { + if (open) { + // Initialize selection with current doctors + setSelectedIds(doctors?.map((d) => d.id) || []) + setSearch('') + } + setIsOpen(open) + }, + [doctors] + ) + + const handleToggleDoctor = useCallback((doctorId: number, checked: boolean) => { + setSelectedIds((prev) => (checked ? [...prev, doctorId] : prev.filter((id) => id !== doctorId))) + }, []) + + const handleSave = useCallback(async () => { + setIsSaving(true) + try { + await onSave(selectedIds) + setIsOpen(false) + } finally { + setIsSaving(false) + } + }, [selectedIds, onSave]) + + const handleNewDoctor = useCallback( + (doctor: DoctorModel) => { + setSelectedIds((prev) => [...prev, doctor.id]) + setAddNewSheetOpen(false) + refetch() + }, + [refetch] + ) + + return ( + <> + + +
+ {isEmpty ? ( +
+ {emptyPlaceholder} + +
+ ) : ( +
+
    + {doctors.map((doctor) => ( +
  • +
    + + e.stopPropagation()} + > + Dr. {doctor.name} + + {doctor.designation && ( + ({doctor.designation}) + )} + +
  • + ))} +
+ {/* Edit hint on hover */} +
+
+ + Edit +
+
+
+ )} +
+
+ +
+
+ + setSearch(e.target.value)} + placeholder={`Search ${doctorTypeLabel}...`} + className="pl-9" + /> +
+
+ + +
+ {filteredDoctors.length === 0 ? ( +

+ No {doctorTypeLabel} found +

+ ) : ( + filteredDoctors.map((doctor) => ( + + )) + )} +
+
+ +
+ +
+ +
+ + +
+
+
+ + {/* Add new doctor sheet */} + + + +
+
+ +
+
+ Add New Doctor +

+ Add a new doctor to the system +

+
+
+
+ +
+ +
+ + + + +
+
+ + ) +} diff --git a/src/renderer/src/components/surgery/inline-edit/InlineEditableField.tsx b/src/renderer/src/components/surgery/inline-edit/InlineEditableField.tsx new file mode 100644 index 0000000..45cb6f6 --- /dev/null +++ b/src/renderer/src/components/surgery/inline-edit/InlineEditableField.tsx @@ -0,0 +1,177 @@ +import { cn } from '@renderer/lib/utils' +import { Button } from '@renderer/components/ui/button' +import { Check, X, Pencil, Plus } from 'lucide-react' +import { useCallback, useEffect, useState, ReactNode } from 'react' + +export interface InlineEditableFieldProps { + /** Whether the field has no content */ + isEmpty: boolean + /** Placeholder text for empty state */ + emptyPlaceholder?: string + /** Whether currently in edit mode */ + isEditing: boolean + /** Called when user wants to enter edit mode */ + onEdit: () => void + /** Called when saving */ + onSave: () => Promise + /** Called when canceling edit */ + onCancel: () => void + /** Whether save is in progress */ + isSaving: boolean + /** Content to display in view mode */ + children: ReactNode + /** Editor content to display in edit mode */ + editor: ReactNode + /** Additional class names */ + className?: string + /** Whether to show action buttons in footer or inline */ + actionsPosition?: 'footer' | 'inline' +} + +export const InlineEditableField = ({ + isEmpty, + emptyPlaceholder = 'Click to add...', + isEditing, + onEdit, + onSave, + onCancel, + isSaving, + children, + editor, + className, + actionsPosition = 'footer' +}: InlineEditableFieldProps) => { + const [saveError, setSaveError] = useState(null) + + const handleSave = useCallback(async () => { + setSaveError(null) + try { + await onSave() + } catch (error) { + setSaveError(error instanceof Error ? error.message : 'Failed to save') + } + }, [onSave]) + + // Keyboard shortcuts + useEffect(() => { + if (!isEditing) return + + const handleKeyDown = (e: KeyboardEvent) => { + // Escape to cancel + if (e.key === 'Escape') { + e.preventDefault() + onCancel() + } + // Cmd/Ctrl + Enter to save + if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + handleSave() + } + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [isEditing, onCancel, handleSave]) + + // View mode + if (!isEditing) { + return ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onEdit() + } + }} + > + {isEmpty ? ( + // Empty state +
+
+ +
+

+ {emptyPlaceholder} +

+
+ ) : ( + // Content with hover hint +
+
{children}
+ {/* Edit hint on hover */} +
+
+ + Click to edit +
+
+
+ )} +
+ ) + } + + // Edit mode + return ( +
+
{editor}
+ + {actionsPosition === 'footer' && ( +
+
+ Esc + to cancel +
+ + {saveError &&

{saveError}

} + +
+ + +
+
+ )} +
+ ) +} diff --git a/src/renderer/src/components/surgery/inline-edit/InlineEditableRichText.tsx b/src/renderer/src/components/surgery/inline-edit/InlineEditableRichText.tsx new file mode 100644 index 0000000..01b739a --- /dev/null +++ b/src/renderer/src/components/surgery/inline-edit/InlineEditableRichText.tsx @@ -0,0 +1,90 @@ +import { cn, isEmptyHtml } from '@renderer/lib/utils' +import { RichTextEditor } from '@renderer/components/common/RichTextEditor' +import { InlineEditableField } from './InlineEditableField' +import { useState, useCallback } from 'react' + +export interface InlineEditableRichTextProps { + /** Current HTML content */ + value: string | null | undefined + /** Called when content is saved */ + onSave: (content: string) => Promise + /** Placeholder for empty state */ + emptyPlaceholder?: string + /** Whether to show template button in editor */ + showTemplateButton?: boolean + /** Additional class name */ + className?: string +} + +// Rich text content display component +const RichTextContent = ({ content, className }: { content: string; className?: string }) => ( +
+) + +export const InlineEditableRichText = ({ + value, + onSave, + emptyPlaceholder = 'Click to add content...', + showTemplateButton = true, + className +}: InlineEditableRichTextProps) => { + const [isEditing, setIsEditing] = useState(false) + const [isSaving, setIsSaving] = useState(false) + const [editedContent, setEditedContent] = useState(value || '') + + const isEmpty = isEmptyHtml(value) + + const handleEdit = useCallback(() => { + setEditedContent(value || '') + setIsEditing(true) + }, [value]) + + const handleCancel = useCallback(() => { + setEditedContent(value || '') + setIsEditing(false) + }, [value]) + + const handleSave = useCallback(async () => { + setIsSaving(true) + try { + await onSave(editedContent) + setIsEditing(false) + } finally { + setIsSaving(false) + } + }, [editedContent, onSave]) + + const handleEditorUpdate = useCallback((content: string) => { + setEditedContent(content) + }, []) + + return ( + + } + > + + + ) +} diff --git a/src/renderer/src/components/surgery/inline-edit/InlineEditableText.tsx b/src/renderer/src/components/surgery/inline-edit/InlineEditableText.tsx new file mode 100644 index 0000000..8f152df --- /dev/null +++ b/src/renderer/src/components/surgery/inline-edit/InlineEditableText.tsx @@ -0,0 +1,144 @@ +import { cn } from '@renderer/lib/utils' +import { Input } from '@renderer/components/ui/input' +import { Button } from '@renderer/components/ui/button' +import { Check, X, Pencil } from 'lucide-react' +import { useState, useCallback, useRef, useEffect, KeyboardEvent } from 'react' + +export interface InlineEditableTextProps { + /** Current value */ + value: string | null | undefined + /** Called when value is saved */ + onSave: (value: string) => Promise + /** Placeholder for empty state */ + emptyPlaceholder?: string + /** Placeholder for input */ + inputPlaceholder?: string + /** Whether the text should be displayed as monospace */ + mono?: boolean + /** Additional class name */ + className?: string +} + +export const InlineEditableText = ({ + value, + onSave, + emptyPlaceholder = 'Click to add...', + inputPlaceholder = 'Enter value...', + mono = false, + className +}: InlineEditableTextProps) => { + const [isEditing, setIsEditing] = useState(false) + const [isSaving, setIsSaving] = useState(false) + const [editedValue, setEditedValue] = useState(value || '') + const inputRef = useRef(null) + + const isEmpty = !value || value.trim().length === 0 + + // Focus input when entering edit mode + useEffect(() => { + if (isEditing && inputRef.current) { + inputRef.current.focus() + inputRef.current.select() + } + }, [isEditing]) + + const handleEdit = useCallback(() => { + setEditedValue(value || '') + setIsEditing(true) + }, [value]) + + const handleCancel = useCallback(() => { + setEditedValue(value || '') + setIsEditing(false) + }, [value]) + + const handleSave = useCallback(async () => { + setIsSaving(true) + try { + await onSave(editedValue) + setIsEditing(false) + } finally { + setIsSaving(false) + } + }, [editedValue, onSave]) + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault() + handleSave() + } else if (e.key === 'Escape') { + e.preventDefault() + handleCancel() + } + }, + [handleSave, handleCancel] + ) + + // View mode + if (!isEditing) { + return ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + handleEdit() + } + }} + > + + {isEmpty ? emptyPlaceholder : value} + + +
+ ) + } + + // Edit mode + return ( +
+ setEditedValue(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={inputPlaceholder} + className={cn('h-8 text-sm', mono && 'font-mono')} + disabled={isSaving} + /> + + +
+ ) +} diff --git a/src/renderer/src/components/surgery/inline-edit/index.ts b/src/renderer/src/components/surgery/inline-edit/index.ts new file mode 100644 index 0000000..7f172a5 --- /dev/null +++ b/src/renderer/src/components/surgery/inline-edit/index.ts @@ -0,0 +1,17 @@ +export { InlineEditableField } from './InlineEditableField' +export type { InlineEditableFieldProps } from './InlineEditableField' + +export { InlineEditableRichText } from './InlineEditableRichText' +export type { InlineEditableRichTextProps } from './InlineEditableRichText' + +export { InlineEditableText } from './InlineEditableText' +export type { InlineEditableTextProps } from './InlineEditableText' + +export { InlineEditableDate } from './InlineEditableDate' +export type { InlineEditableDateProps } from './InlineEditableDate' + +export { InlineEditableDoctors } from './InlineEditableDoctors' +export type { InlineEditableDoctorsProps } from './InlineEditableDoctors' + +export { EditableFieldCard } from './EditableFieldCard' +export type { EditableFieldCardProps } from './EditableFieldCard' diff --git a/src/renderer/src/routes/surgeries/view-surgery.tsx b/src/renderer/src/routes/surgeries/view-surgery.tsx index c59d0f1..c8a9ab9 100644 --- a/src/renderer/src/routes/surgeries/view-surgery.tsx +++ b/src/renderer/src/routes/surgeries/view-surgery.tsx @@ -20,7 +20,7 @@ import { ClipboardList, FileOutput } from 'lucide-react' -import { useEffect, useMemo } from 'react' +import { useEffect, useMemo, useCallback } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import { getPatientByIdQuery } from '../patients/edit-patient' import { queries } from '@renderer/lib/queries' @@ -29,7 +29,7 @@ import { PatientModel } from 'src/shared/models/PatientModel' import { Badge } from '@renderer/components/ui/badge' import { AddOrEditFollowup } from '@renderer/components/surgery/AddOrEditFollowup' import { Card, CardContent, CardHeader, CardTitle } from '@renderer/components/ui/card' -import { cn, formatDate, formatDateTime, isEmptyHtml, unwrapResult } from '@renderer/lib/utils' +import { cn, formatDateTime, unwrapResult } from '@renderer/lib/utils' import { Skeleton } from '@renderer/components/ui/skeleton' import { FollowupModel } from 'src/shared/models/FollowupModel' import { @@ -43,16 +43,24 @@ import { AlertDialogTitle, AlertDialogTrigger } from '@renderer/components/ui/alert-dialog' -import { DoctorModel } from '@shared/models/DoctorModel' import { useSettings } from '@renderer/contexts/SettingsContext' import { createSurgeryContext, createFollowupContext } from '@renderer/lib/print' import { PrintDialog } from '@renderer/components/print/PrintDialog' +import { toast } from '@renderer/components/ui/sonner' + +import { + InlineEditableRichText, + InlineEditableText, + InlineEditableDate, + InlineEditableDoctors, + EditableFieldCard +} from '@renderer/components/surgery/inline-edit' const getSurgeryByIdQuery = (id: number) => queries.surgeries.get(id) const getSurgeryFollowupsQuery = (surgeryId: number) => queries.surgeries.getFollowups(surgeryId) // ============================================================ -// Rich Text Content Display +// Rich Text Content Display (for followups) // ============================================================ const RichTextContent = ({ content, className }: { content: string; className?: string }) => (
( +const InfoItem = ({ icon: Icon, label, iconBg, iconColor, children }: InfoItemProps) => (

{label}

-

{value || 'N/A'}

+ {children}
) -// ============================================================ -// Doctor List Component -// ============================================================ -interface DoctorListProps { - doctors: DoctorModel[] | undefined - emptyMessage: string -} - -const DoctorList = ({ doctors, emptyMessage }: DoctorListProps) => { - if (!doctors || doctors.length === 0) { - return

{emptyMessage}

- } - - return ( -
    - {doctors.map((doctor) => ( -
  • -
    - - - Dr. {doctor.name} - - {doctor.designation && ( - ({doctor.designation}) - )} - -
  • - ))} -
- ) -} - // ============================================================ // Followup Card Component // ============================================================ @@ -245,6 +217,7 @@ export const ViewSurgery = () => { const { patientId, surgeryId } = useParams() const { setBreadcrumbs } = useBreadcrumbs() const { settings } = useSettings() + const queryClient = useQueryClient() const { data: patient } = useQuery({ ...getPatientByIdQuery(parseInt(patientId!)), @@ -279,6 +252,86 @@ export const ViewSurgery = () => { const noFollowups = !isFollowupLoading && followups && followups.length === 0 + // ============================================================ + // Field Update Mutations + // ============================================================ + const updateSurgeryMutation = useMutation({ + mutationFn: async (data: { field: string; value: unknown }) => { + if (!surgery) throw new Error('Surgery not found') + const { result, error } = await window.api.invoke('updateSurgery', surgery.id, { + [data.field]: data.value + }) + if (error) throw new Error(error.message) + return result + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queries.surgeries.get(parseInt(surgeryId!)).queryKey }) + toast.success('Updated successfully') + }, + onError: (error) => { + toast.error(`Failed to update: ${error.message}`) + } + }) + + const updateDoctorsDoneByMutation = useMutation({ + mutationFn: async (doctorIds: number[]) => { + if (!surgery) throw new Error('Surgery not found') + return unwrapResult(window.api.invoke('updateSurgeryDoctorsDoneBy', surgery.id, doctorIds)) + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queries.surgeries.get(parseInt(surgeryId!)).queryKey }) + toast.success('Surgeons updated') + }, + onError: (error) => { + toast.error(`Failed to update surgeons: ${error.message}`) + } + }) + + const updateDoctorsAssistedByMutation = useMutation({ + mutationFn: async (doctorIds: number[]) => { + if (!surgery) throw new Error('Surgery not found') + return unwrapResult(window.api.invoke('updateSurgeryDoctorsAssistedBy', surgery.id, doctorIds)) + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queries.surgeries.get(parseInt(surgeryId!)).queryKey }) + toast.success('Assistants updated') + }, + onError: (error) => { + toast.error(`Failed to update assistants: ${error.message}`) + } + }) + + // ============================================================ + // Field Update Handlers + // ============================================================ + const handleUpdateTextField = useCallback( + (field: string) => async (value: string) => { + await updateSurgeryMutation.mutateAsync({ field, value }) + }, + [updateSurgeryMutation] + ) + + const handleUpdateDateField = useCallback( + (field: string) => async (value: Date | null) => { + await updateSurgeryMutation.mutateAsync({ field, value: value ? +value : null }) + }, + [updateSurgeryMutation] + ) + + const handleUpdateDoneBy = useCallback( + async (doctorIds: number[]) => { + await updateDoctorsDoneByMutation.mutateAsync(doctorIds) + }, + [updateDoctorsDoneByMutation] + ) + + const handleUpdateAssistedBy = useCallback( + async (doctorIds: number[]) => { + await updateDoctorsAssistedByMutation.mutateAsync(doctorIds) + }, + [updateDoctorsAssistedByMutation] + ) + return ( { onClick={() => navigate(`/patients/${patientId}/surgeries/${surgeryId}/edit`)} > - Edit + Edit All } @@ -341,39 +394,66 @@ export const ViewSurgery = () => { + > + + + > + + + > + + + > + + + > + +
@@ -395,131 +475,97 @@ export const ViewSurgery = () => {

Done By

- +

Assisted By

- -
- - - - - {/* Notes Row */} -
- {/* Operative Notes Card */} - - -
-
- -
- - Operative Notes - -
-
- - {!isEmptyHtml(surgery.notes) ? ( -
- -
- ) : ( - - )} -
-
- - {/* Post-Op Notes Card */} - - -
-
- -
- - Post-Operative Care - +
-
- - {!isEmptyHtml(surgery.post_op_notes) ? ( -
- -
- ) : ( - - )} -
-
- - {/* Inward Management Card */} - - -
-
- -
- - Inward Management - -
-
- - {!isEmptyHtml(surgery.inward_management) ? ( -
- -
- ) : ( - - )}
- {/* Discharge Plan Card - only show if content exists */} - {!isEmptyHtml(surgery.discharge_plan) && ( - - -
-
- -
- - Discharge Plan - -
-
- -
- -
-
-
- )} - - {/* Referral Card - only show if content exists */} - {!isEmptyHtml(surgery.referral) && ( - - -
-
- -
- - Referral - -
-
- -
- -
-
-
- )} + {/* Operative Notes Card */} + + + + + {/* Post-Op Notes Card */} + + + + + {/* Inward Management Card */} + + + + + {/* Discharge Plan Card - Always visible */} + + + + + {/* Referral Card - Always visible */} + + + {/* Follow-ups Card */}