diff --git a/lib/components/Button.tsx b/lib/components/Button.tsx index fe5c62bbc..5ea7d8132 100644 --- a/lib/components/Button.tsx +++ b/lib/components/Button.tsx @@ -5,7 +5,7 @@ import { tx } from '../twind' // TODO: add more variants // TODO: this could be matched to some kind of tailwind/twind custom colors // TODO: what about a "link color" and a "black/gray" color? -const colors = ['accent', 'accent-secondary', 'positive', 'negative', 'neutral', 'none'] as const // TODO: this should be named differently, for example: "accent", "good"/"positive", "average"/"neutral", "bad"/"negative" +const colors = ['accent', 'accent-secondary', 'positive', 'negative', 'neutral', 'warn', 'none'] as const // TODO: this should be named differently, for example: "accent", "good"/"positive", "average"/"neutral", "bad"/"negative" const variants = ['primary', 'secondary', 'tertiary', 'textButton'] as const export type ButtonProps = PropsWithChildren<{ @@ -62,6 +62,7 @@ const Button = ({ 'text-white bg-hw-positive-400 hover:bg-hw-positive-500 focus:ring-hw-positive-400': variant === 'primary' && color === 'positive' && !disabled, 'text-white bg-hw-negative-400 hover:bg-hw-negative-500 focus:ring-hw-negative-400': variant === 'primary' && color === 'negative' && !disabled, 'text-white bg-gray-400 hover:bg-gray-500 focus:ring-gray-400': variant === 'primary' && color === 'neutral' && !disabled, // TODO: maybe blue or yellow? + 'text-white bg-hw-warn-400 hover:bg-hw-warn-500 focus:ring-hw-warn-400': variant === 'primary' && color === 'warn' && !disabled, // secondary & {accent, accent-secondary, positive, negative, neutral} 'text-hw-primary-400 bg-hw-primary-100 hover:bg-hw-primary-200 focus:ring-hw-primary-100': variant === 'secondary' && color === 'accent' && !disabled, @@ -69,6 +70,7 @@ const Button = ({ 'text-hw-positive-400 bg-hw-positive-100 hover:bg-hw-positive-200 focus:ring-hw-positive-100': variant === 'secondary' && color === 'positive' && !disabled, 'text-hw-negative-400 bg-hw-negative-100 hover:bg-hw-negative-200 focus:ring-hw-negative-100': variant === 'secondary' && color === 'negative' && !disabled, 'text-gray-500 bg-gray-200 hover:bg-gray-300 focus:ring-gray-200': variant === 'secondary' && color === 'neutral' && !disabled, // TODO: maybe blue or yellow? + 'text-hw-warn-400 bg-hw-warn-200 hover:bg-hw-warn-300 focus:ring-hw-warn-200': variant === 'secondary' && color === 'warn' && !disabled, // tertiary & {accent, accent-secondary, positive, negative, neutral} 'text-hw-primary-400 hover:underline focus:ring-hw-primary-100': variant === 'tertiary' && color === 'accent' && !disabled, @@ -76,6 +78,7 @@ const Button = ({ 'text-hw-positive-400 hover:underline focus:ring-hw-positive-200': variant === 'tertiary' && color === 'positive' && !disabled, 'text-hw-negative-400 hover:underline focus:ring-hw-negative-200': variant === 'tertiary' && color === 'negative' && !disabled, 'text-gray-500 hover:underline focus:ring-gray-300': variant === 'tertiary' && color === 'neutral' && !disabled, // TODO: maybe blue or yellow? + 'text-hw-warn-400 hover:underline focus:ring-hw-warn-200': variant === 'tertiary' && color === 'warn' && !disabled, // text button & {accent, accent-secondary, positive, negative, neutral} 'text-hw-primary-400 hover:text-hw-primary-500 focus:ring-0': variant === 'textButton' && color === 'accent' && !disabled, @@ -83,6 +86,7 @@ const Button = ({ 'text-hw-positive-400 hover:text-hw-positive-500 focus:ring-0': variant === 'textButton' && color === 'positive' && !disabled, 'text-hw-negative-500 hover:text-hw-negative-600 focus:ring-0': variant === 'textButton' && color === 'negative' && !disabled, 'text-gray-500 hover:text-gray-500 focus:ring-0': variant === 'textButton' && color === 'neutral' && !disabled, // TODO: maybe blue or yellow? + 'text-hw-warn-400 hover:text-hw-warn-500 focus:ring-0': variant === 'textButton' && color === 'warn' && !disabled, // {small, medium, large} 'TODO1': size === 'small', // TODO: add styles for small buttons diff --git a/lib/components/Card.tsx b/lib/components/Card.tsx index d20be0d57..b15ff5ec8 100644 --- a/lib/components/Card.tsx +++ b/lib/components/Card.tsx @@ -1,10 +1,10 @@ import { tx } from '../twind' -import type { PropsWithChildren } from 'react' +import type { MouseEventHandler, PropsWithChildren } from 'react' import type { Class } from '@twind/core' export type CardProps = { isSelected?: boolean, - onTileClick?: () => void, + onTileClick?: MouseEventHandler | undefined, className?: Class[] | string } diff --git a/lib/components/Table.tsx b/lib/components/Table.tsx index ba45eaf39..ab0358b6b 100644 --- a/lib/components/Table.tsx +++ b/lib/components/Table.tsx @@ -52,14 +52,14 @@ export const isDataObjectSelected = (tableState: TableState, dataObject: T, export const pageForItem = (data: T[], item: T, entriesPerPage: number, identifierMapping: IdentifierMapping) => { const index = data.findIndex(value => identifierMapping(value) === identifierMapping(item)) - if (index) { + if (index !== -1) { return Math.floor(index / entriesPerPage) } - console.error("item doesn't exist on data", item) + console.warn("item doesn't exist on data", item) return 0 } -export const addElementTableStateUpdate = (tableState: TableState, data: T[], dataObject: T, identifierMapping: IdentifierMapping) => { +export const addElementToTable = (tableState: TableState, data: T[], dataObject: T, identifierMapping: IdentifierMapping) => { return { ...tableState, pagination: tableState.pagination ? { ...tableState.pagination, currentPage: pageForItem(data, dataObject, tableState.pagination.entriesPerPage, identifierMapping) } : undefined, @@ -206,8 +206,8 @@ export const Table = ({ entriesPerPage = Math.max(1, tableState.pagination.entriesPerPage) pageCount = Math.ceil(sortedData.length / entriesPerPage) - if (tableState.pagination.currentPage < 0 || tableState.pagination.currentPage >= pageCount) { - console.error('tableState.pagination.currentPage < 0 || tableState.pagination.currentPage >= pageCount must be fullfilled', + if (tableState.pagination.currentPage < 0 || (tableState.pagination.currentPage >= pageCount && pageCount !== 0)) { + console.error('tableState.pagination.currentPage < 0 || (tableState.pagination.currentPage >= pageCount && pageCount !== 0) must be fullfilled', [`pageCount: ${pageCount}`, `tableState.pagination.currentPage: ${tableState.pagination.currentPage}`]) } else { currentPage = tableState.pagination.currentPage diff --git a/lib/components/examples/TableExample.tsx b/lib/components/examples/TableExample.tsx index 2722bd391..fca21e887 100644 --- a/lib/components/examples/TableExample.tsx +++ b/lib/components/examples/TableExample.tsx @@ -1,6 +1,6 @@ import type { TableProps, TableSortingFunctionType, TableSortingType, TableState } from '../Table' import { - addElementTableStateUpdate, + addElementToTable, defaultTableStatePagination, defaultTableStateSelection, removeFromTableSelection, @@ -136,7 +136,7 @@ const TableExample = ({ data: initialData }: Pick, 'data'>) const withNewData = [...data, newData] const sorted = sortingKey ? withNewData.sort(sortingFunctions[sortingKey][ascending]) : withNewData setData(sorted) - setTableState(addElementTableStateUpdate(tableState, sorted, newData, idMapping)) + setTableState(addElementToTable(tableState, sorted, newData, idMapping)) }} > {'Add Data'} diff --git a/lib/components/user_input/Input.tsx b/lib/components/user_input/Input.tsx index 92e93f647..d79dcb343 100644 --- a/lib/components/user_input/Input.tsx +++ b/lib/components/user_input/Input.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import type { HTMLInputTypeAttribute, InputHTMLAttributes } from 'react' import { tw, tx } from '../../twind' import { Span } from '../Span' +import useSaveDelay from '../../hooks/useSaveDelay' const noop = () => { /* noop */ } @@ -23,7 +24,8 @@ type InputProps = { * @default noop */ onChange?: (text: string) => void, - className?: string + className?: string, + onEditCompleted?: (text: string) => void } & Omit, 'id' | 'value' | 'label' | 'type' | 'onChange' | 'crossOrigin'> /** @@ -38,8 +40,12 @@ const ControlledInput = ({ label, onChange = noop, className = '', + onEditCompleted, + onBlur, ...restProps }: InputProps) => { + const { restartTimer, clearUpdateTimer } = useSaveDelay(() => undefined, 3000) + return (
{label && } @@ -48,7 +54,24 @@ const ControlledInput = ({ id={id} type={type} className={tx('block rounded-md w-full border-gray-300 shadow-sm focus:outline-none focus:border-indigo-500 focus:ring-indigo-500', className)} - onChange={e => onChange(e.target.value)} + onBlur={event => { + if (onBlur) { + onBlur(event) + } + if (onEditCompleted) { + onEditCompleted(event.target.value) + } + }} + onChange={e => { + const value = e.target.value + if (onEditCompleted) { + restartTimer(() => { + onEditCompleted(value) + clearUpdateTimer() + }) + } + onChange(value) + }} {...restProps} />
diff --git a/tasks/hooks/useSaveDelay.ts b/lib/hooks/useSaveDelay.ts similarity index 100% rename from tasks/hooks/useSaveDelay.ts rename to lib/hooks/useSaveDelay.ts diff --git a/lib/twind/config.ts b/lib/twind/config.ts index 27bfdcf55..dae6f8cea 100644 --- a/lib/twind/config.ts +++ b/lib/twind/config.ts @@ -71,6 +71,16 @@ const negative = { 800: '#804D4D', } as const +const warn = { + 200: '#FEEACB', + 300: '#FAB060', + 400: '#EA9E40', + 500: '#D77E30', + 600: '#C48435', + 700: '#AD6915', + 800: '#996628', +} as const + export const config = defineConfig({ theme: { extend: { @@ -79,6 +89,7 @@ export const config = defineConfig({ 'hw-secondary': secondary, 'hw-positive': positive, 'hw-negative': negative, + 'hw-warn': warn, 'hw-neutral': { // TODO: 300 is still missing, see figma 400: '#FF9933' @@ -137,11 +148,11 @@ export const config = defineConfig({ mobile: { max: '1350px' }, }, animation: { - fade: 'fadeOut 3s ease-in-out', - "wave-big-left-up": 'bigLeftUp 1.7s ease-in 0s infinite normal', - "wave-big-right-down": 'bigRightDown 1.7s ease-in 0s infinite reverse', - "wave-small-left-up": 'smallLeftUp 1.7s ease-in 0s infinite normal', - "wave-small-right-down": 'smallRightDown 1.7s ease-in 0s infinite reverse', + 'fade': 'fadeOut 3s ease-in-out', + 'wave-big-left-up': 'bigLeftUp 1.7s ease-in 0s infinite normal', + 'wave-big-right-down': 'bigRightDown 1.7s ease-in 0s infinite reverse', + 'wave-small-left-up': 'smallLeftUp 1.7s ease-in 0s infinite normal', + 'wave-small-right-down': 'smallRightDown 1.7s ease-in 0s infinite reverse', }, keyframes: { fadeOut: { diff --git a/tasks/components/KanbanColumn.tsx b/tasks/components/KanbanColumn.tsx index 10794b07a..c2462bba0 100644 --- a/tasks/components/KanbanColumn.tsx +++ b/tasks/components/KanbanColumn.tsx @@ -10,7 +10,8 @@ import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' -import type { TaskDTO, TaskStatus } from '../mutations/room_mutations' +import type { TaskDTO } from '../mutations/task_mutations' +import type { TaskStatus } from '@helpwave/proto-ts/proto/services/task_svc/v1/task_svc_pb' import { Plus } from 'lucide-react' type KanbanColumnsTranslation = { @@ -51,16 +52,14 @@ export const KanbanColumn = ({ id: type, }) - const taskState = { unscheduled: TaskState.unscheduled, inProgress: TaskState.inProgress, done: TaskState.done } - return (
- + diff --git a/tasks/components/RoomList.tsx b/tasks/components/RoomList.tsx index 988f0a5e3..4771e1f8d 100644 --- a/tasks/components/RoomList.tsx +++ b/tasks/components/RoomList.tsx @@ -1,21 +1,30 @@ -import type { CoreCell } from '@tanstack/react-table' -import { - useReactTable, - createColumnHelper, - getCoreRowModel, - getPaginationRowModel -} from '@tanstack/react-table' import { tw } from '@helpwave/common/twind' import type { Languages } from '@helpwave/common/hooks/useLanguage' import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation' import { useTranslation } from '@helpwave/common/hooks/useTranslation' -import { useState } from 'react' +import { useContext, useEffect, useState } from 'react' import { ConfirmDialog } from '@helpwave/common/components/modals/ConfirmDialog' -import { Pagination } from '@helpwave/common/components/Pagination' import { Button } from '@helpwave/common/components/Button' import { Input } from '@helpwave/common/components/user_input/Input' -import { Checkbox } from '@helpwave/common/components/user_input/Checkbox' import { Span } from '@helpwave/common/components/Span' +import { OrganizationOverviewContext } from '../pages/organizations/[uuid]' +import type { RoomOverviewDTO } from '../mutations/room_mutations' +import { + emptyRoomOverview, + useRoomCreateMutation, + useRoomDeleteMutation, + useRoomOverviewsQuery, + useRoomUpdateMutation +} from '../mutations/room_mutations' +import type { + TableState +} from '@helpwave/common/components/Table' +import { + changeTableSelectionSingle, + defaultTableStatePagination, + defaultTableStateSelection, removeFromTableSelection, + Table +} from '@helpwave/common/components/Table' type RoomListTranslation = { edit: string, @@ -58,230 +67,171 @@ const defaultRoomListTranslations: Record = { rooms: 'Räume', addRoom: 'Raum hinzufügen', bedCount: 'Bettenanzahl', - dangerZoneText: (single) => `Das Löschen von ${single ? defaultRoomListTranslations.de.room : defaultRoomListTranslations.de.rooms} ist permanent und kann nicht rückgängig gemacht werden. Vorsicht!`, - deleteConfirmText: (single) => `Wollen Sie wirklich die ausgewählten ${single ? defaultRoomListTranslations.de.room : defaultRoomListTranslations.de.rooms} löschen?`, + dangerZoneText: (single) => `Das Löschen von ${single ? `einem ${defaultRoomListTranslations.de.room}` : defaultRoomListTranslations.de.rooms} ist permanent und kann nicht rückgängig gemacht werden. Vorsicht!`, + deleteConfirmText: (single) => `Wollen Sie wirklich ${single ? 'den' : 'die'} ausgewählten ${single ? defaultRoomListTranslations.de.room : defaultRoomListTranslations.de.rooms} löschen?`, } } -type Room = { - bedCount: number, - name: string -} - export type RoomListProps = { - rooms: Room[], - roomsPerPage?: number, - onChange: (rooms: Room[]) => void + rooms?: RoomOverviewDTO[], // TODO replace with more optimized RoonDTO + roomsPerPage?: number } -const columnHelper = createColumnHelper() - -const columns = [ - columnHelper.display({ - id: 'select', - }), - columnHelper.accessor('name', { - id: 'name', - }), - columnHelper.accessor('bedCount', { - id: 'bedCount', - }), - columnHelper.display({ - id: 'remove', - }), -] - /** * A table for showing and editing the rooms within a ward */ export const RoomList = ({ language, - roomsPerPage = 5, - rooms, - onChange + rooms }: PropsWithLanguage) => { const translation = useTranslation(language, defaultRoomListTranslations) + const context = useContext(OrganizationOverviewContext) + const [tableState, setTableState] = useState({ + pagination: defaultTableStatePagination, + selection: defaultTableStateSelection + }) + const [usedRooms, setUsedRooms] = useState(rooms ?? []) + const [focusElement, setFocusElement] = useState() + const [isEditing, setIsEditing] = useState(false) - type ConfirmDialogState = { - display: boolean, - single: CoreCell | null - } - const defaultState: ConfirmDialogState = { display: false, single: null } - const [stateDeletionConfirmDialog, setDeletionConfirmDialogState] = useState(defaultState) - const resetDeletionConfirmDialogState = () => setDeletionConfirmDialogState(defaultState) + const identifierMapping = (dataObject: RoomOverviewDTO) => dataObject.id + const creatRoomMutation = useRoomCreateMutation((room) => { + context.updateContext({ ...context.state }) + setFocusElement({ ...emptyRoomOverview, id: room.id }) + }, context.state.wardID ?? '') // Not good but should be safe most of the time + const deleteRoomMutation = useRoomDeleteMutation(() => context.updateContext({ ...context.state })) + const updateRoomMutation = useRoomUpdateMutation(() => context.updateContext({ ...context.state })) + + const { data, isError, isLoading } = useRoomOverviewsQuery(context.state.wardID) // TODO use a more light weight query + + useEffect(() => { + if (data && !isEditing) { + setUsedRooms(data) + } + }, [data, isEditing]) + const [isShowingDeletionConfirmDialog, setDeletionConfirmDialogState] = useState(false) const minRoomNameLength = 1 const maxRoomNameLength = 32 - const table = useReactTable({ - data: rooms, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - initialState: { pagination: { pageSize: roomsPerPage } } - }) - const addRoom = () => { const defaultBedCount = 3 // TODO remove below for an actual room add const newRoom = { - name: 'room' + (rooms.length + 1), - bedCount: defaultBedCount, - isSelected: false + id: '', + name: 'room' + (usedRooms.length + 1), } - onChange([...rooms, newRoom]) + creatRoomMutation.mutate(newRoom) } + // TODO add view for loading + if (isLoading) { + return
Loading Widget
+ } + + // TODO add view for error or error handling + if (isError) { + return
Error Message
+ } + + const hasSelectedMultiple = !!tableState.selection && tableState.selection?.currentSelection.length > 1 + return (
resetDeletionConfirmDialogState()} - onBackgroundClick={() => resetDeletionConfirmDialogState()} + title={translation.deleteConfirmText(!hasSelectedMultiple)} + description={translation.dangerZoneText(!hasSelectedMultiple)} + isOpen={isShowingDeletionConfirmDialog} + onCancel={() => setDeletionConfirmDialogState(false)} + onBackgroundClick={() => setDeletionConfirmDialogState(false)} onConfirm={() => { - if (stateDeletionConfirmDialog.single) { - onChange(rooms.filter(value => value !== stateDeletionConfirmDialog.single?.row.original)) - } else { - table.toggleAllRowsSelected(false) - onChange(rooms.filter(value => !table.getSelectedRowModel().rows.find(row => row.original === value))) - } - resetDeletionConfirmDialogState() + const toDeleteElements = usedRooms.filter(value => tableState.selection?.currentSelection.includes(identifierMapping(value))) + toDeleteElements.forEach(value => deleteRoomMutation.mutate(value.id)) + setTableState(removeFromTableSelection(tableState, toDeleteElements, usedRooms.length, identifierMapping)) + setDeletionConfirmDialogState(false) }} confirmType="negative" />
- {translation.rooms + ` (${rooms.length})`} + {translation.rooms + ` (${usedRooms.length})`}
- {(table.getIsSomePageRowsSelected() || table.getIsAllRowsSelected()) && ( - + {(tableState.selection && tableState.selection?.currentSelection.length > 0) && ( + )}
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ))} - {table.getState().pagination.pageIndex === (table.getPageCount() - 1) && table.getPageCount() > 1 - && (rooms.length % roomsPerPage) !== 0 - && ([...Array((roomsPerPage - (rooms.length % roomsPerPage)) % roomsPerPage)].map((i, index) => ( - - {[table.getAllColumns.length].map((j, index) => ( - - )))} - -
- {header.isPlaceholder - ? null - : { - select: - (
- table.toggleAllRowsSelected()} - /> -
- ), - name: - (
- {translation.roomName} -
), - bedCount: - (
- {translation.bedCount} -
), - remove: (
), - }[header.column.id] - } -
- {{ - bedCount: ( -
- onChange(rooms.map(value => value.name === cell.row.original.name ? { - ...cell.row.original, - bedCount: parseInt(text) - } : value))} - min={1} - id={cell.row.original.bedCount.toString()} - /> -
- ), - name: ( -
- onChange(rooms.map(value => value.name === cell.row.original.name ? { - ...cell.row.original, - name: text - } : value))} - id={cell.row.original.name} - minLength={minRoomNameLength} - maxLength={maxRoomNameLength} - /> -
- ), - remove: ( -
- -
- ), - select: ( -
- cell.row.toggleSelected()} - /> -
- ) - }[cell.column.id]} -
- ))} -
-
- -
+ { + setTableState(tableState) + setFocusElement(undefined) + }]} + identifierMapping={identifierMapping} + header={[ + {translation.roomName}, + {translation.bedCount}, + <> + ]} + rowMappingToCells={dataObject => [ +
+ { + setIsEditing(true) + setUsedRooms(usedRooms.map(value => identifierMapping(value) === identifierMapping(dataObject) ? { + ...dataObject, + name: text + } : value)) + setFocusElement(dataObject) + }} + onEditCompleted={(text) => { + updateRoomMutation.mutate({ + ...dataObject, + name: text + }) + setIsEditing(false) + }} // TODO update to somthing better than blur + id={dataObject.name} + minLength={minRoomNameLength} + maxLength={maxRoomNameLength} + /> +
, +
+ updateRoomMutation.mutate({ + ...dataObject, + // bedCount: parseInt(text) TODO use bedcount change + })} // TODO update to somthing better than blur + min={1} + id={dataObject.id + 'bedCount'} + /> +
, +
+ +
+ ]} + /> ) } diff --git a/tasks/components/RoomOverview.tsx b/tasks/components/RoomOverview.tsx index 0b4391717..dee5d3d68 100644 --- a/tasks/components/RoomOverview.tsx +++ b/tasks/components/RoomOverview.tsx @@ -1,21 +1,33 @@ import { tw } from '@helpwave/common/twind' import { PatientCard } from './cards/PatientCard' import { BedCard } from './cards/BedCard' -import type { BedDTO, RoomDTO } from '../mutations/room_mutations' -import { noop } from '@helpwave/common/components/user_input/Input' +import type { RoomOverviewDTO } from '../mutations/room_mutations' import { Span } from '@helpwave/common/components/Span' +import type { BedMinimalDTO } from '../mutations/bed_mutations' +import { useContext } from 'react' +import { WardOverviewContext } from '../pages/ward/[uuid]' +import type { PatientDTO } from '../mutations/patient_mutations' +import { emptyPatient } from '../mutations/patient_mutations' export type RoomOverviewProps = { - room: RoomDTO, - selected: BedDTO | undefined, - onSelect: (bed: BedDTO) => void, - onAddPatient?: (bed: BedDTO) => void + room: RoomOverviewDTO } /** * A component to show all beds and patients within a room in a ward */ -export const RoomOverview = ({ room, onSelect, selected, onAddPatient = noop }: RoomOverviewProps) => { +export const RoomOverview = ({ room }: RoomOverviewProps) => { + const context = useContext(WardOverviewContext) + + const setSelectedBed = (room: RoomOverviewDTO, bed: BedMinimalDTO, patient: PatientDTO|undefined) => + context.updateContext({ + ...context.state, + room, + bed, + patient + }) + + const selectedBedID = context.state.bed?.id return (
@@ -23,21 +35,37 @@ export const RoomOverview = ({ room, onSelect, selected, onAddPatient = noop }: {room.name}
- {room.beds.map(bed => bed.patient !== undefined ? + {room.beds.map(bed => bed.patient && bed.patient?.id ? ( onSelect(bed)} - isSelected={selected?.id === bed.id} + key={bed.id} + bedIndex={bed.index} + patientName={bed.patient.name} + doneTasks={bed.patient.tasksDone} + inProgressTasks={bed.patient.tasksInProgress} + unscheduledTasks={bed.patient.tasksUnscheduled} + onTileClick={(event) => { + event.stopPropagation() + if (bed.patient) { + // LINTER: `bed.patient.id` gets evaulated as undefined without this if + setSelectedBed(room, bed, { ...emptyPatient, id: bed.patient.id }) + } + }} + isSelected={selectedBedID === bed.id} /> ) : ( onAddPatient(bed)} - isSelected={selected?.id === bed.id}/> + key={bed.id} + bedIndex={bed.index} + onTileClick={(event) => { + event.stopPropagation() + setSelectedBed(room, bed, { + ...emptyPatient, + id: bed.patient?.id ?? '', + humanReadableIdentifier: `Patient ${bed.index}` + }) + }} + isSelected={selectedBedID === bed.id}/> ) )}
diff --git a/tasks/components/SubtaskTile.tsx b/tasks/components/SubtaskTile.tsx index b3b4d5246..34092560d 100644 --- a/tasks/components/SubtaskTile.tsx +++ b/tasks/components/SubtaskTile.tsx @@ -1,10 +1,10 @@ import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation' import { useTranslation } from '@helpwave/common/hooks/useTranslation' import { tw } from '@helpwave/common/twind' -import type { SubTaskDTO } from '../mutations/room_mutations' import { ToggleableInput } from '@helpwave/common/components/user_input/ToggleableInput' import { Checkbox } from '@helpwave/common/components/user_input/Checkbox' import { Button } from '@helpwave/common/components/Button' +import type { SubTaskDTO } from '../mutations/task_mutations' type SubtaskTileTranslation = { subtasks: string, diff --git a/tasks/components/SubtaskView.tsx b/tasks/components/SubtaskView.tsx index c4ba2a177..5e239662d 100644 --- a/tasks/components/SubtaskView.tsx +++ b/tasks/components/SubtaskView.tsx @@ -1,7 +1,6 @@ import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation' import { useTranslation } from '@helpwave/common/hooks/useTranslation' import { tw } from '@helpwave/common/twind' -import type { SubTaskDTO } from '../mutations/room_mutations' import SimpleBarReact from 'simplebar-react' import { Plus } from 'lucide-react' import { Button } from '@helpwave/common/components/Button' @@ -9,6 +8,7 @@ import { SubtaskTile } from './SubtaskTile' import { Span } from '@helpwave/common/components/Span' import { useEffect, useRef, useState } from 'react' import type SimpleBarCore from 'simplebar-core' +import type { SubTaskDTO } from '../mutations/task_mutations' type SubtaskViewTranslation = { subtasks: string, @@ -81,7 +81,7 @@ export const SubtaskView = ({
+
+ + -
) diff --git a/tasks/components/layout/PatientList.tsx b/tasks/components/layout/PatientList.tsx index c91996926..a492d2aba 100644 --- a/tasks/components/layout/PatientList.tsx +++ b/tasks/components/layout/PatientList.tsx @@ -2,15 +2,15 @@ import { tw } from '@helpwave/common/twind' import type { Languages } from '@helpwave/common/hooks/useLanguage' import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation' import { useTranslation } from '@helpwave/common/hooks/useTranslation' -import React, { useState } from 'react' +import React, { useContext, useState } from 'react' import { Button } from '@helpwave/common/components/Button' -import type { PatientDTO } from '../../mutations/room_mutations' import { Span } from '@helpwave/common/components/Span' import { Input } from '@helpwave/common/components/user_input/Input' -import type { PatientWithBedAndRoomDTO } from '../../mutations/patient_mutations' +import type { PatientDTO, PatientWithBedAndRoomDTO } from '../../mutations/patient_mutations' import { usePatientListQuery } from '../../mutations/patient_mutations' import { Label } from '../Label' import { MultiSearchWithMapping, SimpleSearchWithMapping } from '../../utils/simpleSearch' +import { WardOverviewContext } from '../../pages/ward/[uuid]' type PatientListTranslation = { patients: string, @@ -59,7 +59,6 @@ const defaultPatientListTranslations: Record } export type PatientListProps = { - wardUUID: string, onDischarge?: (patient: PatientDTO) => void, width?: number } @@ -68,12 +67,12 @@ export type PatientListProps = { * The right side of the ward/[uuid].tsx page showing the detailed information about the patients in the ward */ export const PatientList = ({ - language, - wardUUID + language }: PropsWithLanguage) => { const translation = useTranslation(language, defaultPatientListTranslations) const [search, setSearch] = useState('') - const { data, isLoading, isError } = usePatientListQuery(wardUUID) + const context = useContext(WardOverviewContext) + const { data, isLoading, isError } = usePatientListQuery(context.state.wardID) if (isError) { return

Error

diff --git a/tasks/components/layout/TaskDetailView.tsx b/tasks/components/layout/TaskDetailView.tsx index 6f1a627b7..1c888a0b5 100644 --- a/tasks/components/layout/TaskDetailView.tsx +++ b/tasks/components/layout/TaskDetailView.tsx @@ -1,5 +1,4 @@ import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation' -import type { TaskDTO } from '../../mutations/room_mutations' import { useTranslation } from '@helpwave/common/hooks/useTranslation' import { tw } from '@helpwave/common/twind' import { ToggleableInput } from '@helpwave/common/components/user_input/ToggleableInput' @@ -19,6 +18,7 @@ import { Input } from '@helpwave/common/components/user_input/Input' import type { Languages } from '@helpwave/common/hooks/useLanguage' import { usePersonalTaskTemplateQuery, useWardTaskTemplateQuery } from '../../mutations/task_template_mutations' import { useAuth } from '../../hooks/useAuth' +import type { TaskDTO } from '../../mutations/task_mutations' type TaskDetailViewTranslation = { close: string, diff --git a/tasks/components/layout/WardDetails.tsx b/tasks/components/layout/WardDetails.tsx index 0a4796035..64baf94c2 100644 --- a/tasks/components/layout/WardDetails.tsx +++ b/tasks/components/layout/WardDetails.tsx @@ -2,7 +2,7 @@ import { tw, tx } from '@helpwave/common/twind' import type { Languages } from '@helpwave/common/hooks/useLanguage' import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation' import { useTranslation } from '@helpwave/common/hooks/useTranslation' -import { useState } from 'react' +import { useContext, useEffect, useState } from 'react' import { ColumnTitle } from '../ColumnTitle' import { Button } from '@helpwave/common/components/Button' import { ConfirmDialog } from '@helpwave/common/components/modals/ConfirmDialog' @@ -10,8 +10,16 @@ import { RoomList } from '../RoomList' import { WardForm } from '../WardForm' import { Span } from '@helpwave/common/components/Span' import { TaskTemplateWardPreview } from '../TaskTemplateWardPreview' -import type { WardDetailDTO, WardDTO } from '../../mutations/ward_mutations' -import { useWardTaskTemplateQuery } from '../../mutations/task_template_mutations' +import type { WardDetailDTO } from '../../mutations/ward_mutations' + +import { + emptyWard, + useWardCreateMutation, + useWardDeleteMutation, + useWardUpdateMutation, + useWardDetailsQuery +} from '../../mutations/ward_mutations' +import { OrganizationOverviewContext } from '../../pages/organizations/[uuid]' type WardDetailTranslation = { updateWard: string, @@ -23,7 +31,8 @@ type WardDetailTranslation = { deleteConfirmText: string, deleteWard: string, create: string, - update: string + update: string, + roomsNotOnCreate: string } const defaultWardDetailTranslations: Record = { @@ -37,7 +46,8 @@ const defaultWardDetailTranslations: Record = deleteConfirmText: 'Do you really want to delete this ward?', deleteWard: 'Delete ward', create: 'Create', - update: 'Update' + update: 'Update', + roomsNotOnCreate: 'Rooms can only be added once the ward is created' }, de: { updateWard: 'Station ändern', @@ -49,15 +59,13 @@ const defaultWardDetailTranslations: Record = deleteConfirmText: 'Wollen Sie wirklich diese Station löschen?', deleteWard: 'Station Löschen', create: 'Erstellen', - update: 'Ändern' + update: 'Ändern', + roomsNotOnCreate: 'Räume können erst hinzugefügt werden, wenn der Ward erstellt wurde' } } export type WardDetailProps = { - ward: WardDetailDTO, - onCreate: (ward: WardDTO) => void, - onUpdate: (ward: WardDTO) => void, - onDelete: (ward: WardDTO) => void, + ward?: WardDetailDTO, width?: number } @@ -68,24 +76,46 @@ export type WardDetailProps = { export const WardDetail = ({ language, ward, - onCreate, - onUpdate, - onDelete, width }: PropsWithLanguage) => { const translation = useTranslation(language, defaultWardDetailTranslations) - const isCreatingNewOrganization = ward.id === '' + const context = useContext(OrganizationOverviewContext) + const { data, isError, isLoading } = useWardDetailsQuery(context.state.wardID) + + const isCreatingNewWard = !context.state.wardID const [isShowingConfirmDialog, setIsShowingConfirmDialog] = useState(false) - const [filledRequired, setFilledRequired] = useState(!isCreatingNewOrganization) - const [newWard, setNewWard] = useState(ward) + const [filledRequired, setFilledRequired] = useState(!isCreatingNewWard) + const [newWard, setNewWard] = useState(emptyWard) + + useEffect(() => { + if (data && !isCreatingNewWard) { + setNewWard(data) + } + }, [data, isCreatingNewWard]) + + const createWardMutation = useWardCreateMutation((ward) => context.updateContext({ ...context.state, wardID: ward.id })) + const updateWardMutation = useWardUpdateMutation((ward) => { + context.updateContext({ ...context.state, wardID: ward.id }) + setNewWard({ ...newWard, name: ward.name }) + }) + const deleteWardMutation = useWardDeleteMutation(() => context.updateContext({ ...context.state, wardID: undefined })) - const { data, isLoading, isError } = useWardTaskTemplateQuery(ward.id) // the value of how much space a TaskTemplateCard and the surrounding gap requires, given in px const minimumWidthOfCards = 200 const columns = width === undefined ? 3 : Math.max(Math.floor(width / minimumWidthOfCards), 1) + // TODO add view for loading + if (isLoading && ward) { + return
Loading Widget
+ } + + // TODO add view for error or error handling + if (isError && ward) { + return
Error Message
+ } + return (
setIsShowingConfirmDialog(false)} onConfirm={() => { setIsShowingConfirmDialog(false) - onDelete(newWard) + deleteWardMutation.mutate(newWard.id) }} confirmType="negative" />
-
-
- ({ name: room.name, bedCount: room.beds.length }))} - onChange={(rooms) => setNewWard({ ...newWard })} // TODO: Figure out out to apple the diff + isShowingErrorsDirectly={!isCreatingNewWard} />
+ {isCreatingNewWard ? + {translation.roomsNotOnCreate} + : ( +
+ +
+ )}
{newWard.id !== '' && (
- {/* TODO show something here */} - {(isLoading || isError) && ''} - {data && } +
) } -
+
{translation.dangerZone} {translation.dangerZoneText}
diff --git a/tasks/components/layout/WardRoomList.tsx b/tasks/components/layout/WardRoomList.tsx index f98fee51b..3f3a13d7b 100644 --- a/tasks/components/layout/WardRoomList.tsx +++ b/tasks/components/layout/WardRoomList.tsx @@ -1,43 +1,43 @@ import { tw } from '@helpwave/common/twind' import { RoomOverview } from '../RoomOverview' -import type { BedDTO, RoomDTO } from '../../mutations/room_mutations' +import type { RoomOverviewDTO } from '../../mutations/room_mutations' +import type { BedWithPatientWithTasksNumberDTO } from '../../mutations/bed_mutations' +import { useContext } from 'react' +import { WardOverviewContext } from '../../pages/ward/[uuid]' +import { useRoomOverviewsQuery } from '../../mutations/room_mutations' export type WardRoomListProps = { - selectedBed: BedDTO, - setSelectedBed: (bed: BedDTO) => void, - setIsShowingPatientDialog: (isShowing: boolean) => void, - rooms: RoomDTO[] + selectedBed?: BedWithPatientWithTasksNumberDTO, + rooms?: RoomOverviewDTO[] } /** * The left side component of the page showing all Rooms of a Ward */ export const WardRoomList = ({ - selectedBed, - setSelectedBed, - setIsShowingPatientDialog, rooms }: WardRoomListProps) => { + const context = useContext(WardOverviewContext) + const { data, isError, isLoading } = useRoomOverviewsQuery(context.state.wardID) + + if (isError) { + return
Error in WardRoomList!
+ } + + if (isLoading) { + return
Loading WardRoomList!
+ } + + rooms ??= data + return ( -
+
context.updateContext({ wardID: context.state.wardID })} + > {rooms.map(room => ( { - setSelectedBed({ - ...bed, - patient: { - id: Math.random().toString(), - note: '', - humanReadableIdentifier: 'Patient ' + (room.beds.findIndex(value => value.id === bed?.id) + 1), - tasks: [] - } - }) - setIsShowingPatientDialog(true) - }} /> ) )} diff --git a/tasks/components/pill/PillLabel.tsx b/tasks/components/pill/PillLabel.tsx index fc26d73d3..721586bb6 100644 --- a/tasks/components/pill/PillLabel.tsx +++ b/tasks/components/pill/PillLabel.tsx @@ -2,6 +2,8 @@ import { tw } from '@helpwave/common/twind' import type { Languages } from '@helpwave/common/hooks/useLanguage' import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation' import { useTranslation } from '@helpwave/common/hooks/useTranslation' +import { TaskStatus } from '@helpwave/proto-ts/proto/services/task_svc/v1/task_svc_pb' +import { Span } from '@helpwave/common/components/Span' type PillLabelTranslation = { text: string @@ -10,27 +12,35 @@ type PillLabelTranslation = { export type TaskStateInformation = { colorLabel: string, translation: Record } const TaskState = { - unscheduled: { + [TaskStatus.TASK_STATUS_TODO]: { colorLabel: 'hw-label-1', translation: { en: { text: 'unscheduled' }, de: { text: 'Nicht Geplant' } } }, - inProgress: { + [TaskStatus.TASK_STATUS_IN_PROGRESS]: { colorLabel: 'hw-label-2', translation: { en: { text: 'in progress' }, de: { text: 'In Arbeit' } } }, - done: { + [TaskStatus.TASK_STATUS_DONE]: { colorLabel: 'hw-label-3', translation: { en: { text: 'done' }, de: { text: 'Fertig' } } }, + // TODO change this + [TaskStatus.TASK_STATUS_UNSPECIFIED]: { + colorLabel: 'hw-label-1', + translation: { + en: { text: 'unscheduled' }, + de: { text: 'Nicht Geplant' } + } + }, } as const export type PillLabelProps = { @@ -44,16 +54,16 @@ export type PillLabelProps = { const PillLabel = ({ language, count = 0, - state = TaskState.unscheduled + state = TaskState[TaskStatus.TASK_STATUS_TODO] }: PropsWithLanguage) => { const translation = useTranslation(language, state.translation) return ( -
- {translation.text} + {translation.text}
{count}
diff --git a/tasks/components/pill/PillLabelsColumn.tsx b/tasks/components/pill/PillLabelsColumn.tsx index 4451ae737..423d9355a 100644 --- a/tasks/components/pill/PillLabelsColumn.tsx +++ b/tasks/components/pill/PillLabelsColumn.tsx @@ -1,5 +1,6 @@ import { tw } from '@helpwave/common/twind' import { PillLabel, TaskState } from './PillLabel' +import { TaskStatus } from '@helpwave/proto-ts/proto/services/task_svc/v1/task_svc_pb' export type PillLabelsColumnProps = { unscheduledCount?: number, @@ -13,9 +14,9 @@ export type PillLabelsColumnProps = { const PillLabelsColumn = ({ unscheduledCount = 0, inProgressCount = 0, doneCount = 0 }: PillLabelsColumnProps) => { return (
- - - + + +
) } diff --git a/tasks/components/user_input/TaskStatusSelect.tsx b/tasks/components/user_input/TaskStatusSelect.tsx index 90cd3a39c..4df09b0bd 100644 --- a/tasks/components/user_input/TaskStatusSelect.tsx +++ b/tasks/components/user_input/TaskStatusSelect.tsx @@ -1,7 +1,8 @@ -import type { TaskStatus } from '../../mutations/room_mutations' import { Select } from '@helpwave/common/components/user_input/Select' import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation' import { useTranslation } from '@helpwave/common/hooks/useTranslation' +import { GetPatientDetailsResponse } from '@helpwave/proto-ts/proto/services/task_svc/v1/patient_svc_pb' +import TaskStatus = GetPatientDetailsResponse.TaskStatus; type TaskStatusSelectTranslation = { unscheduled: string, @@ -45,9 +46,9 @@ export const TaskStatusSelect = ({