feat(web): 班次管理 Web UI — Phase 2a-2
新增班次管理前端页面,接入后端 12 条孤立路由: - API 模块: shifts.ts(班次 CRUD + 患者分配 + 批量分配 + 交接日志) - 列表页: ShiftList.tsx(日期/班次/状态筛选 + 统计概览) - 详情页: ShiftDetail.tsx(班次信息 + 患者分配 Tab + 交接记录 Tab) - 路由注册: /health/shifts + /health/shifts/:id 权限: health.shifts.list / health.shifts.manage
This commit is contained in:
247
apps/web/src/api/health/shifts.ts
Normal file
247
apps/web/src/api/health/shifts.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import client from '../client';
|
||||
import type { PaginatedResponse } from '../types';
|
||||
|
||||
// --- Types ---
|
||||
|
||||
export interface Shift {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
shift_date: string;
|
||||
period: string;
|
||||
nurse_id?: string;
|
||||
status: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
version: number;
|
||||
patient_count?: number;
|
||||
critical_count?: number;
|
||||
attention_count?: number;
|
||||
}
|
||||
|
||||
export interface PatientAssignment {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
shift_id: string;
|
||||
patient_id: string;
|
||||
care_level: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
version: number;
|
||||
patient_name?: string;
|
||||
}
|
||||
|
||||
export interface HandoffLog {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
from_shift_id: string;
|
||||
to_shift_id: string;
|
||||
patient_id: string;
|
||||
notes?: string;
|
||||
pending_items?: Record<string, unknown>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
version: number;
|
||||
patient_name?: string;
|
||||
}
|
||||
|
||||
export interface CreateShiftReq {
|
||||
shift_date: string;
|
||||
period: string;
|
||||
nurse_id?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface UpdateShiftReq {
|
||||
shift_date?: string;
|
||||
period?: string;
|
||||
nurse_id?: string;
|
||||
status?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface ListShiftsParams {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
shift_date?: string;
|
||||
period?: string;
|
||||
nurse_id?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface CreatePatientAssignmentReq {
|
||||
patient_id: string;
|
||||
care_level?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface BatchAssignReq {
|
||||
patient_ids: string[];
|
||||
care_level?: string;
|
||||
}
|
||||
|
||||
export interface UpdatePatientAssignmentReq {
|
||||
care_level?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface CreateHandoffReq {
|
||||
from_shift_id: string;
|
||||
to_shift_id: string;
|
||||
patient_id: string;
|
||||
notes?: string;
|
||||
pending_items?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ListHandoffParams {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
from_shift_id?: string;
|
||||
to_shift_id?: string;
|
||||
}
|
||||
|
||||
// --- Constants ---
|
||||
|
||||
export const PERIOD_OPTIONS = [
|
||||
{ label: '上午班', value: 'morning' },
|
||||
{ label: '下午班', value: 'afternoon' },
|
||||
{ label: '晚班', value: 'evening' },
|
||||
{ label: '夜班', value: 'night' },
|
||||
];
|
||||
|
||||
export const SHIFT_STATUS_OPTIONS = [
|
||||
{ label: '待开始', value: 'scheduled' },
|
||||
{ label: '进行中', value: 'in_progress' },
|
||||
{ label: '已完成', value: 'completed' },
|
||||
{ label: '已取消', value: 'cancelled' },
|
||||
];
|
||||
|
||||
export const CARE_LEVEL_OPTIONS = [
|
||||
{ label: '稳定', value: 'stable' },
|
||||
{ label: '需关注', value: 'attention' },
|
||||
{ label: '危重', value: 'critical' },
|
||||
];
|
||||
|
||||
export const PERIOD_LABEL: Record<string, string> = Object.fromEntries(
|
||||
PERIOD_OPTIONS.map((o) => [o.value, o.label]),
|
||||
);
|
||||
|
||||
export const SHIFT_STATUS_LABEL: Record<string, string> = Object.fromEntries(
|
||||
SHIFT_STATUS_OPTIONS.map((o) => [o.value, o.label]),
|
||||
);
|
||||
|
||||
export const SHIFT_STATUS_COLOR: Record<string, string> = {
|
||||
scheduled: 'default',
|
||||
in_progress: 'processing',
|
||||
completed: 'success',
|
||||
cancelled: 'error',
|
||||
};
|
||||
|
||||
export const CARE_LEVEL_LABEL: Record<string, string> = Object.fromEntries(
|
||||
CARE_LEVEL_OPTIONS.map((o) => [o.value, o.label]),
|
||||
);
|
||||
|
||||
export const CARE_LEVEL_COLOR: Record<string, string> = {
|
||||
stable: 'green',
|
||||
attention: 'orange',
|
||||
critical: 'red',
|
||||
};
|
||||
|
||||
// --- API ---
|
||||
|
||||
export const shiftApi = {
|
||||
// --- Shifts ---
|
||||
|
||||
list: async (params: ListShiftsParams) => {
|
||||
const { data } = await client.get<{
|
||||
success: boolean;
|
||||
data: PaginatedResponse<Shift>;
|
||||
}>('/health/shifts', { params });
|
||||
return data.data;
|
||||
},
|
||||
|
||||
get: async (shiftId: string) => {
|
||||
const { data } = await client.get<{
|
||||
success: boolean;
|
||||
data: Shift;
|
||||
}>(`/health/shifts/${shiftId}`);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
create: async (req: CreateShiftReq) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: Shift;
|
||||
}>('/health/shifts', req);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
update: async (shiftId: string, req: UpdateShiftReq & { version: number }) => {
|
||||
const { data } = await client.put<{
|
||||
success: boolean;
|
||||
data: Shift;
|
||||
}>(`/health/shifts/${shiftId}`, req);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
delete: async (shiftId: string, version: number) => {
|
||||
await client.delete(`/health/shifts/${shiftId}`, { data: { version } });
|
||||
},
|
||||
|
||||
// --- Assignments ---
|
||||
|
||||
listAssignments: async (shiftId: string, params?: { page?: number; page_size?: number }) => {
|
||||
const { data } = await client.get<{
|
||||
success: boolean;
|
||||
data: PaginatedResponse<PatientAssignment>;
|
||||
}>(`/health/shifts/${shiftId}/assignments`, { params });
|
||||
return data.data;
|
||||
},
|
||||
|
||||
createAssignment: async (shiftId: string, req: CreatePatientAssignmentReq) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: PatientAssignment;
|
||||
}>(`/health/shifts/${shiftId}/assignments`, req);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
batchAssign: async (shiftId: string, req: BatchAssignReq) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: PatientAssignment[];
|
||||
}>(`/health/shifts/${shiftId}/assignments/batch`, req);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
updateAssignment: async (shiftId: string, assignmentId: string, req: UpdatePatientAssignmentReq & { version: number }) => {
|
||||
const { data } = await client.put<{
|
||||
success: boolean;
|
||||
data: PatientAssignment;
|
||||
}>(`/health/shifts/${shiftId}/assignments/${assignmentId}`, req);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
deleteAssignment: async (shiftId: string, assignmentId: string, version: number) => {
|
||||
await client.delete(`/health/shifts/${shiftId}/assignments/${assignmentId}`, { data: { version } });
|
||||
},
|
||||
|
||||
// --- Handoff Logs ---
|
||||
|
||||
listHandoffs: async (params?: ListHandoffParams) => {
|
||||
const { data } = await client.get<{
|
||||
success: boolean;
|
||||
data: PaginatedResponse<HandoffLog>;
|
||||
}>('/health/handoff-logs', { params });
|
||||
return data.data;
|
||||
},
|
||||
|
||||
createHandoff: async (req: CreateHandoffReq) => {
|
||||
const { data } = await client.post<{
|
||||
success: boolean;
|
||||
data: HandoffLog;
|
||||
}>('/health/handoff-logs', req);
|
||||
return data.data;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user