From 9131588936630b939095fc45f95d8e3cb18773f9 Mon Sep 17 00:00:00 2001 From: yznahmad Date: Thu, 24 Jul 2025 12:39:15 +0300 Subject: [PATCH] 2est-end --- .dockerignore | 71 + .env.production | 38 + .eslintrc.cjs | 84 + .gitignore | 9 + .vscode/settings.json | 3 + DEPLOYMENT.md | 236 + Dockerfile | 87 + app/components/DashboardLayout.tsx | 221 + app/components/FormModal.tsx | 113 + app/components/ReportFormModal.tsx | 753 + app/components/ReportViewModal.tsx | 407 + app/components/Toast.tsx | 56 + app/entry.client.tsx | 18 + app/entry.server.tsx | 140 + app/root.tsx | 45 + app/routes/_index.tsx | 11 + app/routes/areas.tsx | 283 + app/routes/dashboard.$.tsx | 26 + app/routes/dashboard.tsx | 183 + app/routes/dredger-locations.tsx | 381 + app/routes/employees.tsx | 549 + app/routes/equipment.tsx | 369 + app/routes/foreman.tsx | 276 + app/routes/health.tsx | 37 + app/routes/logout.tsx | 10 + app/routes/mail-settings.tsx | 266 + app/routes/reclamation-locations.tsx | 281 + app/routes/reports.tsx | 676 + app/routes/reports_.new.tsx | 736 + app/routes/reset-password.tsx | 326 + app/routes/signin.tsx | 144 + app/routes/signup.tsx | 217 + app/routes/test-email.tsx | 131 + app/tailwind.css | 43 + app/utils/auth.server.ts | 129 + app/utils/db.server.ts | 19 + app/utils/excelExport.ts | 568 + app/utils/mail.server.ts | 200 + deploy.sh | 157 + docker-compose.yml | 98 + package-lock.json | 14070 ++++++++++++++++ package.json | 57 + postcss.config.js | 6 + prisma/dev.db | Bin 0 -> 86016 bytes .../20250719155324_init/migration.sql | 77 + .../migration.sql | 1 + .../migration.sql | 24 + .../migration.sql | 13 + .../migration.sql | 13 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 99 + prisma/seed.ts | 158 + public/alhaffer-logo-en.png | Bin 0 -> 22063 bytes public/clogo-sm.png | Bin 0 -> 796194 bytes public/favicon.ico | Bin 0 -> 16958 bytes public/logo-dark.png | Bin 0 -> 7200 bytes public/logo-light.png | Bin 0 -> 7200 bytes tailwind.config.ts | 22 + tsconfig.json | 32 + vite.config.ts | 24 + 60 files changed, 22996 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.production create mode 100644 .eslintrc.cjs create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 DEPLOYMENT.md create mode 100644 Dockerfile create mode 100644 app/components/DashboardLayout.tsx create mode 100644 app/components/FormModal.tsx create mode 100644 app/components/ReportFormModal.tsx create mode 100644 app/components/ReportViewModal.tsx create mode 100644 app/components/Toast.tsx create mode 100644 app/entry.client.tsx create mode 100644 app/entry.server.tsx create mode 100644 app/root.tsx create mode 100644 app/routes/_index.tsx create mode 100644 app/routes/areas.tsx create mode 100644 app/routes/dashboard.$.tsx create mode 100644 app/routes/dashboard.tsx create mode 100644 app/routes/dredger-locations.tsx create mode 100644 app/routes/employees.tsx create mode 100644 app/routes/equipment.tsx create mode 100644 app/routes/foreman.tsx create mode 100644 app/routes/health.tsx create mode 100644 app/routes/logout.tsx create mode 100644 app/routes/mail-settings.tsx create mode 100644 app/routes/reclamation-locations.tsx create mode 100644 app/routes/reports.tsx create mode 100644 app/routes/reports_.new.tsx create mode 100644 app/routes/reset-password.tsx create mode 100644 app/routes/signin.tsx create mode 100644 app/routes/signup.tsx create mode 100644 app/routes/test-email.tsx create mode 100644 app/tailwind.css create mode 100644 app/utils/auth.server.ts create mode 100644 app/utils/db.server.ts create mode 100644 app/utils/excelExport.ts create mode 100644 app/utils/mail.server.ts create mode 100644 deploy.sh create mode 100644 docker-compose.yml create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 prisma/dev.db create mode 100644 prisma/migrations/20250719155324_init/migration.sql create mode 100644 prisma/migrations/20250723203304_add_email_to_employee/migration.sql create mode 100644 prisma/migrations/20250723204123_add_email_to_employeenpx/migration.sql create mode 100644 prisma/migrations/20250723223146_add_mail_settings/migration.sql create mode 100644 prisma/migrations/20250723234904_add_password_reset_tokens/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.ts create mode 100644 public/alhaffer-logo-en.png create mode 100644 public/clogo-sm.png create mode 100644 public/favicon.ico create mode 100644 public/logo-dark.png create mode 100644 public/logo-light.png create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c1a0279 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,71 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Production build +/build +/public/build + +# Environment files +.env +.env.local +.env.development +.env.test +.env.production + +# Database files +*.db +*.db-journal +/data +/backups + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage + +# IDE files +.vscode +.idea +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Documentation +README.md +*.md + +# Scripts +deploy.sh +*.sh + +# Temporary files +tmp +temp \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..38dcf20 --- /dev/null +++ b/.env.production @@ -0,0 +1,38 @@ +# Production Environment Variables +# Copy this file and rename to .env for production deployment +# Make sure to change all default values for security + +# Application Settings +NODE_ENV=production +APP_PORT=3000 +DOMAIN=your-domain.com + +# Database +DATABASE_URL="file:/app/data/production.db" + +# Security +SESSION_SECRET="your-super-secure-session-secret-change-this-in-production-min-32-chars" + +# Super Admin Account (created on first run) +SUPER_ADMIN="superadmin" +SUPER_ADMIN_EMAIL="admin@yourcompany.com" +SUPER_ADMIN_PASSWORD="YourSecurePassword123!" + +# Storage Paths (for bind mounts) +DATA_PATH=./data +BACKUP_PATH=./backups + +# Backup Schedule (cron format) +BACKUP_SCHEDULE="0 2 * * *" + +# Mail Settings (optional - for password reset features) +MAIL_HOST="" +MAIL_PORT="587" +MAIL_SECURE="false" +MAIL_USERNAME="" +MAIL_PASSWORD="" +MAIL_FROM_NAME="Phosphat Report System" +MAIL_FROM_EMAIL="" + +# Logging (optional) +LOG_LEVEL="info" \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..4f6f59e --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,84 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + ignorePatterns: ["!**/.server", "!**/.client"], + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + "import/resolver": { + typescript: {}, + }, + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.cjs"], + env: { + node: true, + }, + }, + ], +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4354aa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules + +/.cache +/build +.env + +/generated/prisma + +/generated/prisma diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..eabd0c4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.autoClosingTags": false +} \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..d4ccbe7 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,236 @@ +# Phosphat Report - Production Deployment Guide + +This guide will help you deploy the Phosphat Report application on your Dockploy VPS using Docker Compose. + +## Prerequisites + +- Docker and Docker Compose installed on your VPS +- Domain name configured (optional but recommended) +- SSL certificate (handled by Traefik if using reverse proxy) + +## Quick Start + +1. **Clone the repository** to your VPS: + ```bash + git clone + cd phosphat-report + ``` + +2. **Configure environment variables**: + ```bash + cp .env.production .env + nano .env # Edit with your production values + ``` + +3. **Deploy the application**: + ```bash + chmod +x deploy.sh + ./deploy.sh deploy + ``` + +## Environment Configuration + +Edit the `.env` file with your production values: + +### Required Settings +```env +# Change these values for security +SESSION_SECRET="your-super-secure-session-secret-min-32-chars" +SUPER_ADMIN_PASSWORD="YourSecurePassword123!" +SUPER_ADMIN_EMAIL="admin@yourcompany.com" + +# Your domain (for Traefik labels) +DOMAIN=your-domain.com +``` + +### Optional Settings +```env +# Custom port (default: 3000) +APP_PORT=3000 + +# Storage paths +DATA_PATH=./data +BACKUP_PATH=./backups + +# Email configuration (for password reset) +MAIL_HOST=smtp.gmail.com +MAIL_USERNAME=your-email@gmail.com +MAIL_PASSWORD=your-app-password +``` + +## Deployment Commands + +The `deploy.sh` script provides several useful commands: + +```bash +# Deploy application +./deploy.sh deploy + +# Stop services +./deploy.sh stop + +# Restart services +./deploy.sh restart + +# View logs +./deploy.sh logs + +# Create database backup +./deploy.sh backup + +# Check status +./deploy.sh status +``` + +## Manual Deployment + +If you prefer manual deployment: + +```bash +# Create directories +mkdir -p data backups logs + +# Build and start services +docker-compose up -d --build + +# Check status +docker-compose ps +docker-compose logs app +``` + +## Dockploy Integration + +For Dockploy deployment: + +1. **Create a new application** in Dockploy +2. **Set the repository** URL +3. **Configure environment variables** in Dockploy UI +4. **Set build command**: `docker-compose build` +5. **Set start command**: `docker-compose up -d` + +### Dockploy Environment Variables + +Add these in the Dockploy environment variables section: + +``` +NODE_ENV=production +SESSION_SECRET=your-super-secure-session-secret +SUPER_ADMIN=superadmin +SUPER_ADMIN_EMAIL=admin@yourcompany.com +SUPER_ADMIN_PASSWORD=YourSecurePassword123! +DOMAIN=your-domain.com +``` + +## Database Management + +### Backup +```bash +# Manual backup +docker-compose exec app cp /app/data/production.db /app/data/backup_$(date +%Y%m%d_%H%M%S).db + +# Automated backup (runs daily at 2 AM) +# Configured in docker-compose.yml backup service +``` + +### Restore +```bash +# Stop application +docker-compose stop app + +# Restore database +cp backups/backup_YYYYMMDD_HHMMSS.db data/production.db + +# Start application +docker-compose start app +``` + +## Monitoring + +### Health Check +```bash +curl http://localhost:3000/health +``` + +### Logs +```bash +# Application logs +docker-compose logs -f app + +# All services logs +docker-compose logs -f +``` + +### Resource Usage +```bash +docker stats phosphat-report-app +``` + +## Reverse Proxy (Traefik) + +The application includes Traefik labels for automatic SSL and routing. If you're using Traefik: + +1. Ensure Traefik is running on your server +2. Set the `DOMAIN` environment variable +3. The application will be automatically available at `https://your-domain.com` + +## Security Considerations + +1. **Change default passwords** in `.env` +2. **Use strong session secret** (minimum 32 characters) +3. **Enable firewall** on your VPS +4. **Regular backups** are configured automatically +5. **Keep Docker images updated** + +## Troubleshooting + +### Application won't start +```bash +# Check logs +docker-compose logs app + +# Check database permissions +ls -la data/ + +# Rebuild without cache +docker-compose build --no-cache +``` + +### Database issues +```bash +# Reset database (WARNING: This will delete all data) +docker-compose down +rm -f data/production.db +docker-compose up -d +``` + +### Performance issues +```bash +# Check resource usage +docker stats + +# Increase memory limits in docker-compose.yml +# Optimize database queries +``` + +## Updates + +To update the application: + +```bash +# Pull latest code +git pull origin main + +# Rebuild and restart +docker-compose up -d --build + +# Check logs +docker-compose logs -f app +``` + +## Support + +For issues and support: +1. Check the logs: `docker-compose logs app` +2. Verify environment variables +3. Check database connectivity +4. Review health endpoint: `/health` \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e1284e5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,87 @@ +# Use Node.js 20 Alpine for smaller image size +FROM node:20-alpine AS base + +# Install system dependencies +RUN apk add --no-cache \ + libc6-compat \ + openssl \ + sqlite \ + wget \ + dumb-init + +# Set working directory +WORKDIR /app + +# Install dependencies only when needed +FROM base AS deps +# Copy package files +COPY package.json package-lock.json* ./ +RUN npm ci --only=production --frozen-lockfile && npm cache clean --force + +# Rebuild the source code only when needed +FROM base AS builder +# Copy package files +COPY package.json package-lock.json* ./ +RUN npm ci --frozen-lockfile + +# Copy source code +COPY . . + +# Generate Prisma client +RUN npx prisma generate + +# Build the application +RUN npm run build + +# Production image, copy all the files and run the app +FROM base AS runner + +# Create a non-root user +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 remix + +# Copy built application +COPY --from=builder --chown=remix:nodejs /app/build ./build +COPY --from=builder --chown=remix:nodejs /app/public ./public +COPY --from=builder --chown=remix:nodejs /app/package.json ./package.json +COPY --from=builder --chown=remix:nodejs /app/prisma ./prisma + +# Copy production dependencies +COPY --from=deps --chown=remix:nodejs /app/node_modules ./node_modules + +# Create necessary directories +RUN mkdir -p /app/data /app/logs && \ + chown -R remix:nodejs /app/data /app/logs + +# Create startup script +COPY --chown=remix:nodejs <; +} + +export default function DashboardLayout({ children, user }: DashboardLayoutProps) { + return ( +
+ {/* Navigation */} + + + {/* Sidebar */} +
+
+ +
+ + {/* Main content */} +
+ {children} +
+
+
+ ); +} \ No newline at end of file diff --git a/app/components/FormModal.tsx b/app/components/FormModal.tsx new file mode 100644 index 0000000..836421b --- /dev/null +++ b/app/components/FormModal.tsx @@ -0,0 +1,113 @@ +import { Form } from "@remix-run/react"; +import { useEffect, useRef } from "react"; + +interface FormModalProps { + isOpen: boolean; + onClose: () => void; + title: string; + children: React.ReactNode; + isSubmitting?: boolean; + submitText?: string; + onSubmit?: () => void; +} + +export default function FormModal({ + isOpen, + onClose, + title, + children, + isSubmitting = false, + submitText = "Save", + onSubmit +}: FormModalProps) { + const modalRef = useRef(null); + + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape") { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener("keydown", handleEscape); + document.body.style.overflow = "hidden"; + } + + return () => { + document.removeEventListener("keydown", handleEscape); + document.body.style.overflow = "unset"; + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+
+ {/* Background overlay */} +
+ + {/* Modal panel */} +
+
+
+
+

+ {title} +

+ +
+ +
+ {children} +
+
+
+ +
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/components/ReportFormModal.tsx b/app/components/ReportFormModal.tsx new file mode 100644 index 0000000..ff639b4 --- /dev/null +++ b/app/components/ReportFormModal.tsx @@ -0,0 +1,753 @@ +import React from 'react'; +import { Form } from "@remix-run/react"; + +interface ReportFormModalProps { + isOpen: boolean; + onClose: () => void; + isEditing: boolean; + isSubmitting: boolean; + editingReport: any; + actionData: any; + areas: any[]; + dredgerLocations: any[]; + reclamationLocations: any[]; + foremen: any[]; + equipment: any[]; + timeSheetEntries: any[]; + stoppageEntries: any[]; + addTimeSheetEntry: () => void; + removeTimeSheetEntry: (id: string) => void; + updateTimeSheetEntry: (id: string, field: string, value: string) => void; + addStoppageEntry: () => void; + removeStoppageEntry: (id: string) => void; + updateStoppageEntry: (id: string, field: string, value: string) => void; +} + +export default function ReportFormModal({ + isOpen, + onClose, + isEditing, + isSubmitting, + editingReport, + actionData, + areas, + dredgerLocations, + reclamationLocations, + foremen, + equipment, + timeSheetEntries, + stoppageEntries, + addTimeSheetEntry, + removeTimeSheetEntry, + updateTimeSheetEntry, + addStoppageEntry, + removeStoppageEntry, + updateStoppageEntry +}: ReportFormModalProps) { + if (!isOpen) return null; + + return ( +
+
+
+ +
+
+
+

+ {isEditing ? "Edit Report" : "Create New Report"} +

+ +
+ +
+ + {/* Enhanced Modal Footer */} +
+ + +
+
+
+
+ ); +} + +// Basic Information Component +function BasicInformation({ editingReport, actionData, areas, dredgerLocations, reclamationLocations }: any) { + return ( +
+

Basic Information

+ +
+
+ + + {actionData?.errors?.shift && ( +

{actionData.errors.shift}

+ )} +
+ +
+ + + {actionData?.errors?.areaId && ( +

{actionData.errors.areaId}

+ )} +
+
+ +
+
+ + + {actionData?.errors?.dredgerLocationId && ( +

{actionData.errors.dredgerLocationId}

+ )} +
+ +
+ + + {actionData?.errors?.reclamationLocationId && ( +

{actionData.errors.reclamationLocationId}

+ )} +
+
+ +
+
+ + + {actionData?.errors?.dredgerLineLength && ( +

{actionData.errors.dredgerLineLength}

+ )} +
+ +
+ + + {actionData?.errors?.shoreConnection && ( +

{actionData.errors.shoreConnection}

+ )} +
+
+
+ ); +} + +// Reclamation Height Component +function ReclamationHeight({ editingReport }: any) { + return ( +
+

Reclamation Height

+
+
+ + +
+
+ + +
+
+
+ ); +} + +// Pipeline Length Component +function PipelineLength({ editingReport }: any) { + return ( +
+

Pipeline Length

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ ); +} + +// Equipment Statistics Component +function EquipmentStatistics({ editingReport, foremen }: any) { + return ( +
+

Equipment Statistics

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ ); +} + +// TimeSheet Section Component +function TimeSheetSection({ timeSheetEntries, equipment, addTimeSheetEntry, removeTimeSheetEntry, updateTimeSheetEntry }: any) { + return ( +
+
+
+

+ + + + Equipments Time Sheet +

+

Track Equipment working hours and maintenance periods

+
+ +
+ + {timeSheetEntries.length === 0 ? ( +
+ + + +

No Equipment entries yet

+

Click "Add Equipment Entry" to start tracking Equipment hours

+
+ ) : ( +
+ {timeSheetEntries.map((entry: any, index: number) => ( +
+
+ Entry #{index + 1} + +
+ +
+
+ + +
+
+ + updateTimeSheetEntry(entry.id, 'reason', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 text-sm" + placeholder="e.g., Maintenance, Operation" + /> +
+
+ +
+
+ + updateTimeSheetEntry(entry.id, 'from1', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 text-sm" + /> +
+
+ + updateTimeSheetEntry(entry.id, 'to1', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 text-sm" + /> +
+
+ + updateTimeSheetEntry(entry.id, 'from2', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 text-sm" + /> +
+
+ + updateTimeSheetEntry(entry.id, 'to2', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 text-sm" + /> +
+
+ +
+ {entry.total} +
+
+
+
+ ))} +
+ )} +
+ ); +} + +// Stoppages Section Component +function StoppagesSection({ stoppageEntries, addStoppageEntry, removeStoppageEntry, updateStoppageEntry }: any) { + return ( +
+
+
+

+ + + + Operation Stoppages +

+

Record operational interruptions and downtime

+
+ +
+ + {stoppageEntries.length === 0 ? ( +
+ + + +

No stoppages recorded

+

Click "Add Stoppage" to record operational interruptions

+
+ ) : ( +
+ {stoppageEntries.map((entry: any, index: number) => ( +
+
+ Stoppage #{index + 1} + +
+ +
+
+ + updateStoppageEntry(entry.id, 'from', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 text-sm" + /> +
+
+ + updateStoppageEntry(entry.id, 'to', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 text-sm" + /> +
+
+ +
+ {entry.total} +
+
+
+ +
+
+ + updateStoppageEntry(entry.id, 'reason', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 text-sm" + placeholder="e.g., Maintenance, Equipment failure" + /> +
+
+ + updateStoppageEntry(entry.id, 'responsible', e.target.value)} + className="block w-full border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 text-sm" + placeholder="e.g., Maintenance team" + /> +
+
+ +
+ +