Skip to content

feat: implement core hospital workflow system with API and UI#2

Open
pragan333 wants to merge 1 commit intomainfrom
claude/hospital-workflow-optimization-bXnY4
Open

feat: implement core hospital workflow system with API and UI#2
pragan333 wants to merge 1 commit intomainfrom
claude/hospital-workflow-optimization-bXnY4

Conversation

@pragan333
Copy link
Copy Markdown
Owner

Summary

This PR establishes the foundational architecture for the Smart Hospital Workflow Optimization platform (Package 1), implementing a complete full-stack system with database schema, REST API, frontend UI, and shared type definitions.

Key Changes

Database & ORM

  • Added Prisma schema (apps/api/prisma/schema.prisma) with 11 models covering:
    • User authentication and role-based access (ADMIN, DOCTOR, NURSE, RECEPTIONIST)
    • Patient management with admission tracking
    • Bed management with occupancy status and types
    • Task management with priority and status tracking
    • Staff scheduling with shifts and availability
    • Department coordination
    • Patient transfer requests
    • Real-time notifications
  • Implemented database seeding (apps/api/prisma/seed.ts) with sample departments, users, staff, and patients

Backend API (Express.js)

  • Created modular route handlers for 7 feature areas:
    • Auth (auth.routes.ts): JWT-based login/register with bcrypt password hashing
    • Patients (patient.routes.ts): CRUD operations with filtering, search, and pagination
    • Beds (bed.routes.ts): Bed assignment, status management, and occupancy statistics
    • Tasks (task.routes.ts): Task creation, assignment, and status updates with Socket.IO events
    • Staff (staff.routes.ts): Staff management and shift scheduling
    • Departments (department.routes.ts): Department CRUD and patient transfer coordination
    • Notifications (notification.routes.ts): Real-time notification delivery
  • Implemented middleware:
    • JWT authentication with role-based authorization
    • Zod schema validation for all request bodies
    • Global error handling
  • Configured Socket.IO for real-time updates
  • Set up environment configuration and database connection

Frontend (Next.js 14)

  • Created dashboard pages for all major features:
    • Dashboard with summary cards
    • Patients list with status tracking
    • Beds management with occupancy visualization
    • Tasks with priority and status indicators
    • Staff directory with role and shift information
    • Departments overview with occupancy rates
  • Implemented shared UI components:
    • Sidebar navigation with active route highlighting
    • Header with user menu
    • Card component for data display
    • StatusBadge component with color-coded statuses
  • Set up API client utility with token-based authentication
  • Configured Tailwind CSS with custom color palette

Shared Types & Validation

  • Created comprehensive Zod schemas (packages/shared-types/src/schemas.ts) for:
    • Authentication (login, register, token response)
    • Patient operations (create, admit, retrieve)
    • Bed management (create, assign, statistics)
    • Task management (create, update, retrieve)
    • Staff and shift operations
    • Department and transfer operations
    • Notifications
  • Defined TypeScript enums for all domain entities (Role, PatientStatus, BedStatus, Priority, etc.)
  • Exported inferred types from schemas for type-safe API contracts

Infrastructure & Configuration

  • Set up monorepo structure with pnpm workspaces and Turborepo
  • Created Docker Compose configuration for PostgreSQL, API, and web services
  • Configured TypeScript for both Node.js and Next.js environments
  • Set up shared ESLint configuration
  • Added environment variable templates (.env.example)
  • Implemented Dockerfiles for both API and web applications

Documentation

  • Updated CLAUDE.md with complete project overview, tech stack, and development status
  • Updated README.md with setup instructions, API endpoints, and feature descriptions

Notable Implementation Details

  • Type Safety: Full end-to-end type safety using Zod schemas shared between frontend and backend
  • Real-time Updates: Socket.IO integration for live bed status, task updates, and notifications
  • Role-Based Access: Authorization middleware enforces role-based permissions on protected routes
  • Pagination & Filtering: Patient and task endpoints support pagination, filtering, and search
  • Database Relations: Comprehensive Prisma relations enable efficient data fetching with includes
  • Error Handling: Centralized error handler with proper HTTP status codes
  • **

https://claude.ai/code/session_01LXmx7H3ihtz3eyWBia2dTU

Full-stack monorepo with pnpm workspaces + Turborepo for 3 parallel developer workstreams:
- Developer A: Patient flow & auth (Express + JWT + Prisma)
- Developer B: Bed management & task board (Socket.IO real-time)
- Developer C: Staff scheduling & department coordination

Includes: Next.js 14 frontend, Express API with 7 modules, PostgreSQL schema,
shared Zod types, Docker setup, and seed data.

https://claude.ai/code/session_01LXmx7H3ihtz3eyWBia2dTU
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf05d3da23

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

});

// GET /api/departments/:id
router.get("/:id", async (req: Request, res: Response) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move transfer list route before /:id matcher

Because router.get("/:id") is registered before router.get("/transfers"), a request to GET /api/departments/transfers is consumed by the /:id handler (id = "transfers") and never reaches the transfer-list logic. In practice this makes the list endpoint unreachable and returns department-not-found behavior instead of transfer data.

Useful? React with 👍 / 👎.

Comment on lines +29 to +31
const notification = await prisma.notification.update({
where: { id: req.params.id },
data: { isRead: true },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce recipient ownership when marking notifications read

This update only filters by notification id, so any authenticated user who can obtain another user's notification ID can mark it as read. The read-all endpoint already scopes by recipientId, so this single-item endpoint should apply the same ownership check to prevent cross-user state changes.

Useful? React with 👍 / 👎.

Comment on lines +160 to +163
const staff = await prisma.staff.findUnique({ where: { userId: req.user!.userId } });
const transfer = await prisma.transferRequest.update({
where: { id: req.params.id },
data: { status: "REJECTED", approvedById: staff?.id, resolvedAt: new Date() },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require staff authorization for transfer rejection

The reject path looks up the caller's staff record but never enforces that one exists before updating the transfer, unlike the approve path which returns 403 for non-staff users. As written, any authenticated account can reject transfer requests, which breaks the intended permission boundary for transfer decisions.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR bootstraps a full-stack “Smart Hospital Workflow Optimization” monorepo, introducing core backend (Express + Prisma + Socket.IO), frontend (Next.js + Tailwind), and a shared types/validation package to establish an end-to-end foundation.

Changes:

  • Added pnpm workspace + Turborepo configuration and shared TS/ESLint configs.
  • Implemented initial API (auth, patients, beds, tasks, staff, departments/transfers, notifications) backed by a new Prisma schema + seed data.
  • Added initial Next.js UI shell and feature pages, plus a shared types package with Zod schemas/enums.

Reviewed changes

Copilot reviewed 59 out of 60 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
turbo.json Turborepo task pipeline configuration.
pnpm-workspace.yaml Defines workspace package locations.
package.json Root scripts to run/build/lint/test monorepo.
docker-compose.yml Local orchestration for Postgres + API + web.
.gitignore Ignores node/build/env artifacts.
.env.example Environment variable template for local/dev.
README.md Project documentation and setup instructions.
CLAUDE.md Project conventions and developer guidance.
packages/eslint-config/package.json Shared ESLint config package manifest.
packages/eslint-config/index.js Shared ESLint rules for TS packages.
packages/tsconfig/package.json Shared TS config package manifest.
packages/tsconfig/base.json Base strict TS compiler settings.
packages/tsconfig/node.json Node-focused TS compiler settings.
packages/tsconfig/nextjs.json Next.js-focused TS compiler settings.
packages/shared-types/package.json Shared API contract package manifest.
packages/shared-types/tsconfig.json Build config for shared-types package.
packages/shared-types/src/enums.ts Domain enums for roles/statuses/types.
packages/shared-types/src/schemas.ts Zod schemas for request/DTO validation.
packages/shared-types/src/index.ts Re-exports schemas/enums and inferred types.
apps/api/package.json API dependencies and scripts.
apps/api/tsconfig.json API TypeScript build configuration.
apps/api/Dockerfile Container build for API service.
apps/api/src/config/env.ts API environment configuration.
apps/api/src/config/db.ts Prisma client initialization.
apps/api/src/config/socket.ts Socket.IO server setup and accessor.
apps/api/src/middleware/auth.ts JWT auth + role-based authorization middleware.
apps/api/src/middleware/validate.ts Zod request-body validation middleware.
apps/api/src/middleware/errorHandler.ts Global error handler middleware.
apps/api/src/modules/auth/auth.routes.ts Auth routes (register/login/refresh/me).
apps/api/src/modules/patients/patient.routes.ts Patient CRUD + admit/discharge workflows.
apps/api/src/modules/beds/bed.routes.ts Bed CRUD + assign/release + stats endpoint.
apps/api/src/modules/tasks/task.routes.ts Task CRUD + escalation + Socket.IO events.
apps/api/src/modules/staff/staff.routes.ts Staff CRUD + schedule + shift endpoints.
apps/api/src/modules/departments/department.routes.ts Department CRUD + transfer request endpoints.
apps/api/src/modules/notifications/notification.routes.ts Notifications list + mark-read endpoints.
apps/api/src/app.ts Express app wiring (middleware + module routers).
apps/api/src/server.ts HTTP server bootstrap + Socket.IO init.
apps/api/prisma/schema.prisma Core database schema (users/patients/beds/tasks/etc.).
apps/api/prisma/seed.ts Seed script with demo departments/users/staff/patients/beds.
apps/web/package.json Web app dependencies and scripts.
apps/web/tsconfig.json Web app TypeScript configuration.
apps/web/next.config.js Next config (transpile workspace package).
apps/web/tailwind.config.ts Tailwind configuration + theme colors.
apps/web/postcss.config.js PostCSS config for Tailwind pipeline.
apps/web/Dockerfile Container build for web service.
apps/web/lib/cn.ts Utility to merge Tailwind class names.
apps/web/lib/api.ts Fetch wrapper for API calls with token support.
apps/web/components/ui/Card.tsx UI card component.
apps/web/components/ui/StatusBadge.tsx Status badge with color mapping.
apps/web/components/Sidebar.tsx Sidebar navigation shell.
apps/web/components/Header.tsx Header shell with notification/user stub.
apps/web/app/globals.css Global Tailwind styles.
apps/web/app/layout.tsx Root layout wiring sidebar/header and main content.
apps/web/app/page.tsx Redirects home to dashboard.
apps/web/app/dashboard/page.tsx Dashboard page scaffold with summary cards.
apps/web/app/patients/page.tsx Patients page scaffold (mock data).
apps/web/app/beds/page.tsx Beds page scaffold (mock data).
apps/web/app/tasks/page.tsx Tasks page scaffold (mock data).
apps/web/app/staff/page.tsx Staff page scaffold (mock data).
apps/web/app/departments/page.tsx Departments page scaffold (mock data).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4 to +6
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smart-hospital/shared-types points main/types at ./src/index.ts (TypeScript) and doesn’t define a build step. Since the API runs compiled JS (node dist/server.js), Node will try to load TS from this package at runtime and crash. Emit JS/types to dist/ and update main/types (and optionally exports) to reference the built output; make API build depend on it.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +32
const notification = await prisma.notification.update({
where: { id: req.params.id },
data: { isRead: true },
});
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PATCH /api/notifications/:id/read updates by notification id only, without scoping to recipientId = req.user.userId. Any authenticated user who can guess a UUID could mark someone else’s notification as read. Update with a where clause including recipientId (or fetch+verify ownership) and return 404/403 when it doesn’t belong to the caller.

Suggested change
const notification = await prisma.notification.update({
where: { id: req.params.id },
data: { isRead: true },
});
const result = await prisma.notification.updateMany({
where: { id: req.params.id, recipientId: req.user!.userId },
data: { isRead: true },
});
if (result.count === 0) {
return res.status(404).json({ success: false, error: "Notification not found" });
}
const notification = await prisma.notification.findFirst({
where: { id: req.params.id, recipientId: req.user!.userId },
});

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.yml
Comment on lines +7 to +10
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: smart_hospital
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compose file hardcodes database credentials (POSTGRES_USER/POSTGRES_PASSWORD). Even for local dev, prefer .env-driven configuration (or env var passthrough) to reduce accidental reuse/leakage and make overrides easier across environments.

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.yml
Comment on lines +26 to +30
environment:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/smart_hospital?schema=public"
JWT_SECRET: "change-this-in-production"
JWT_REFRESH_SECRET: "change-this-refresh-in-production"
API_PORT: "4000"
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT secrets are hardcoded in docker-compose.yml (JWT_SECRET, JWT_REFRESH_SECRET). If someone forgets to override these, it’s easy to accidentally deploy with known secrets. Prefer reading them from .env/external env vars and avoid committing defaults that look usable.

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +118
if (bedId) {
await tx.bed.update({
where: { id: bedId },
data: { status: "OCCUPIED", currentPatientId: patientId },
});
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admitting a patient updates the bed to OCCUPIED without validating that the bed exists, is AVAILABLE, and belongs to the target departmentId. This can overwrite an existing assignment and produce inconsistent state. Add checks (ideally inside the transaction) and return a 409/400 when the bed is invalid/unavailable.

Copilot uses AI. Check for mistakes.
Comment on lines +161 to +163
const transfer = await prisma.transferRequest.update({
where: { id: req.params.id },
data: { status: "REJECTED", approvedById: staff?.id, resolvedAt: new Date() },
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PATCH /api/departments/transfers/:id/reject doesn’t enforce that the caller is staff: if no staff record exists, approvedById becomes null and the update still succeeds. This allows any authenticated user to reject transfers. Require a staff profile (and ideally appropriate roles) the same way the approve endpoint does.

Suggested change
const transfer = await prisma.transferRequest.update({
where: { id: req.params.id },
data: { status: "REJECTED", approvedById: staff?.id, resolvedAt: new Date() },
if (!staff) {
res.status(403).json({ success: false, error: "Only staff can reject transfers" });
return;
}
const transfer = await prisma.transferRequest.update({
where: { id: req.params.id },
data: { status: "REJECTED", approvedById: staff.id, resolvedAt: new Date() },

Copilot uses AI. Check for mistakes.
Comment thread apps/api/package.json
"@smart-hospital/tsconfig": "workspace:*",
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@types/express is ^5.0.0 while express is ^4.21.0. Express v5 typings are not compatible with Express 4 in many cases and can cause incorrect types/build errors. Align these by using @types/express@^4.x (or upgrade Express to v5 consistently).

Suggested change
"@types/express": "^5.0.0",
"@types/express": "^4.17.21",

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +16
const { email, password, firstName, lastName, role } = req.body;

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RegisterSchema requires firstName/lastName, but the register handler doesn’t persist them anywhere (they’re only used in the response message). This makes the request contract misleading. Either store these fields (e.g., on User or by creating the Staff profile here) or remove them from the register schema.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +8
export const env = {
PORT: parseInt(process.env.API_PORT || "4000", 10),
DATABASE_URL: process.env.DATABASE_URL!,
JWT_SECRET: process.env.JWT_SECRET || "dev-secret",
JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET || "dev-refresh-secret",
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DATABASE_URL is asserted non-null and JWT secrets fall back to weak defaults (dev-secret, dev-refresh-secret). Consider validating required env vars at startup and failing fast with clear errors; for JWT secrets, require explicit values outside local dev to avoid accidentally running with insecure defaults.

Suggested change
export const env = {
PORT: parseInt(process.env.API_PORT || "4000", 10),
DATABASE_URL: process.env.DATABASE_URL!,
JWT_SECRET: process.env.JWT_SECRET || "dev-secret",
JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET || "dev-refresh-secret",
const isLocalDevelopment =
!process.env.NODE_ENV ||
process.env.NODE_ENV === "development" ||
process.env.NODE_ENV === "dev";
function getRequiredEnvVar(name: string): string {
const value = process.env[name]?.trim();
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
function getJwtSecret(name: string, localDefault: string): string {
const value = process.env[name]?.trim();
if (value) {
return value;
}
if (isLocalDevelopment) {
return localDefault;
}
throw new Error(
`Missing required environment variable: ${name}. Weak development defaults are only allowed in local development.`
);
}
export const env = {
PORT: parseInt(process.env.API_PORT || "4000", 10),
DATABASE_URL: getRequiredEnvVar("DATABASE_URL"),
JWT_SECRET: getJwtSecret("JWT_SECRET", "dev-secret"),
JWT_REFRESH_SECRET: getJwtSecret("JWT_REFRESH_SECRET", "dev-refresh-secret"),

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +51
include: { beds: true },
});

const stats = departments.map((dept) => ({
departmentId: dept.id,
departmentName: dept.name,
total: dept.beds.length,
available: dept.beds.filter((b) => b.status === "AVAILABLE").length,
occupied: dept.beds.filter((b) => b.status === "OCCUPIED").length,
reserved: dept.beds.filter((b) => b.status === "RESERVED").length,
maintenance: dept.beds.filter((b) => b.status === "MAINTENANCE").length,
occupancyRate: dept.beds.length > 0
? Math.round((dept.beds.filter((b) => b.status === "OCCUPIED").length / dept.beds.length) * 100)
: 0,
}));

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GET /api/beds/stats fetches all active departments including full beds arrays and then computes counts in memory. This scales poorly as bed counts grow. Prefer Prisma aggregations/groupBy (or count queries by status) so you only transfer the computed counts.

Suggested change
include: { beds: true },
});
const stats = departments.map((dept) => ({
departmentId: dept.id,
departmentName: dept.name,
total: dept.beds.length,
available: dept.beds.filter((b) => b.status === "AVAILABLE").length,
occupied: dept.beds.filter((b) => b.status === "OCCUPIED").length,
reserved: dept.beds.filter((b) => b.status === "RESERVED").length,
maintenance: dept.beds.filter((b) => b.status === "MAINTENANCE").length,
occupancyRate: dept.beds.length > 0
? Math.round((dept.beds.filter((b) => b.status === "OCCUPIED").length / dept.beds.length) * 100)
: 0,
}));
select: {
id: true,
name: true,
_count: {
select: { beds: true },
},
},
});
const departmentIds = departments.map((dept) => dept.id);
const bedCountsByStatus = departmentIds.length > 0
? await prisma.bed.groupBy({
by: ["departmentId", "status"],
where: {
departmentId: { in: departmentIds },
},
_count: {
_all: true,
},
})
: [];
const countsByDepartment = bedCountsByStatus.reduce<Record<string, {
AVAILABLE: number;
OCCUPIED: number;
RESERVED: number;
MAINTENANCE: number;
}>>((acc, row) => {
if (!acc[row.departmentId]) {
acc[row.departmentId] = {
AVAILABLE: 0,
OCCUPIED: 0,
RESERVED: 0,
MAINTENANCE: 0,
};
}
if (row.status === "AVAILABLE" || row.status === "OCCUPIED" || row.status === "RESERVED" || row.status === "MAINTENANCE") {
acc[row.departmentId][row.status] = row._count._all;
}
return acc;
}, {});
const stats = departments.map((dept) => {
const counts = countsByDepartment[dept.id] ?? {
AVAILABLE: 0,
OCCUPIED: 0,
RESERVED: 0,
MAINTENANCE: 0,
};
const total = dept._count.beds;
const occupied = counts.OCCUPIED;
return {
departmentId: dept.id,
departmentName: dept.name,
total,
available: counts.AVAILABLE,
occupied,
reserved: counts.RESERVED,
maintenance: counts.MAINTENANCE,
occupancyRate: total > 0 ? Math.round((occupied / total) * 100) : 0,
};
});

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants