From 3a038ffc6f3d4612345b5c7131926bcee5de23d8 Mon Sep 17 00:00:00 2001 From: Satyam Rastogi Date: Thu, 21 May 2026 15:30:19 +0530 Subject: [PATCH] subcontractor: add progress updates and task activity timeline --- src/data/mockStore.jsx | 249 ++++++++- .../SubcontractorTaskDetailPage.jsx | 519 ++++++++++++++++-- 2 files changed, 706 insertions(+), 62 deletions(-) diff --git a/src/data/mockStore.jsx b/src/data/mockStore.jsx index cf438be..9c2fbed 100644 --- a/src/data/mockStore.jsx +++ b/src/data/mockStore.jsx @@ -5152,6 +5152,43 @@ const MOCK_SUBCONTRACTOR_TASKS = [ { id: 'sct_001_f1', description: 'Labour — Ridge Cap Replacement', type: 'Labour / Service Fee', amount: 350, createdAt: '2026-05-16T13:20:00Z' }, { id: 'sct_001_f2', description: 'Mobilization to site', type: 'Mobilization Fee', amount: 75, createdAt: '2026-05-16T13:22:00Z' }, ], + activities: [ + { + id: 'sct_001_a1', type: 'status_change', status: 'Assigned', + note: 'Task created and assigned. Materials list shared with crew.', + photos: [], + actorId: 'owner_001', actorName: 'Justin Johnson', actorRole: 'Owner', + at: '2026-05-15T09:12:00Z', + }, + { + id: 'sct_001_a2', type: 'status_change', status: 'In Progress', + note: 'On site, sourced replacement Owens Corning Estate Gray. Starting on the south slope.', + photos: [ + { id: 'sct_001_a2_p1', url: '/assets/images/properties/Hail_Damaged_Shingles.jpg', name: 'arrival.jpg' }, + ], + actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor', + at: '2026-05-16T08:30:00Z', + }, + { + id: 'sct_001_a3', type: 'progress_update', status: 'In Progress', + note: 'First two damaged ridge tiles removed. Decking underneath is clean — no rot.', + photos: [ + { id: 'sct_001_a3_p1', url: '/assets/images/properties/Storm_Worn_Roof.jpg', name: 'decking-clean.jpg' }, + ], + actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor', + at: '2026-05-16T10:45:00Z', + }, + { + id: 'sct_001_a4', type: 'progress_update', status: 'In Progress', + note: 'Replacement ridge caps installed on lower row. Sealing next.', + photos: [ + { id: 'sct_001_a4_p1', url: '/assets/images/properties/Cracked_Storm_Shingles.jpg', name: 'lower-row.jpg' }, + { id: 'sct_001_a4_p2', url: '/assets/images/properties/Overlapping_Roof_Shingles.jpg', name: 'wide-shot.jpg' }, + ], + actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor', + at: '2026-05-16T14:20:00Z', + }, + ], projectId: 'proj_004', paymentStatus: 'Unpaid', paymentRequestedAt: '2026-05-16T17:30:00Z', @@ -5197,6 +5234,53 @@ const MOCK_SUBCONTRACTOR_TASKS = [ fees: [ { id: 'sct_002_f1', description: 'Labour — Day 1 disconnect / prep', type: 'Labour / Service Fee', amount: 425, createdAt: '2026-05-12T17:00:00Z' }, ], + activities: [ + { + id: 'sct_002_a1', type: 'status_change', status: 'Assigned', + note: 'Task created and assigned. Permit pulled, materials staged.', + photos: [], + actorId: 'owner_001', actorName: 'Justin Johnson', actorRole: 'Owner', + at: '2026-05-10T15:40:00Z', + }, + { + id: 'sct_002_a2', type: 'status_change', status: 'In Progress', + note: 'Oncor on site for disconnect. Cutting power to existing 100A panel and starting tear-out.', + photos: [ + { id: 'sct_002_a2_p1', url: '/assets/images/properties/Beige_Two_Story_House.jpg', name: 'oncor-disconnect.jpg' }, + ], + actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor', + at: '2026-05-12T08:20:00Z', + }, + { + id: 'sct_002_a3', type: 'progress_update', status: 'In Progress', + note: 'Old 100A panel removed. Service entrance wires capped and labelled. Mast looks good — no signs of corrosion.', + photos: [ + { id: 'sct_002_a3_p1', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', name: 'mast-check.jpg' }, + { id: 'sct_002_a3_p2', url: '/assets/images/properties/Brick_Front_Porch_Home.jpg', name: 'old-panel-out.jpg' }, + ], + actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor', + at: '2026-05-12T10:30:00Z', + }, + { + id: 'sct_002_a4', type: 'progress_update', status: 'In Progress', + note: 'New 200A Square D panel mounted and bonded. Two new ground rods driven and connected.', + photos: [ + { id: 'sct_002_a4_p1', url: '/assets/images/properties/Sunny_Suburban_House.jpg', name: 'new-panel-mount.jpg' }, + ], + actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor', + at: '2026-05-12T13:15:00Z', + }, + { + id: 'sct_002_a5', type: 'progress_update', status: 'In Progress', + note: 'Migrated 20 of 24 home circuits to the new panel. Tested with multimeter — all live and balanced across legs. Remaining 4 circuits (HVAC + dryer) to be wired tomorrow before re-energizing.', + photos: [ + { id: 'sct_002_a5_p1', url: '/assets/images/properties/Luxury_Driveway_Residence.jpg', name: 'circuits-migrated.jpg' }, + { id: 'sct_002_a5_p2', url: '/assets/images/properties/Overlapping_Roof_Shingles.jpg', name: 'panel-labels.jpg' }, + ], + actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor', + at: '2026-05-12T16:00:00Z', + }, + ], projectId: 'proj_003', paymentStatus: 'Unpaid', paymentRequestedAt: '2026-05-12T17:00:00Z', @@ -5319,6 +5403,33 @@ const MOCK_SUBCONTRACTOR_TASKS = [ fees: [ { id: 'sct_005_f1', description: 'Inspection — initial visit', type: 'Inspection Fee', amount: 125, createdAt: '2026-05-14T13:00:00Z' }, ], + activities: [ + { + id: 'sct_005_a1', type: 'status_change', status: 'Assigned', + note: 'Task created. Customer reports unusually high attic temps.', + photos: [], + actorId: 'ADM01', actorName: 'Admin One', actorRole: 'Admin', + at: '2026-05-12T10:00:00Z', + }, + { + id: 'sct_005_a2', type: 'status_change', status: 'In Progress', + note: 'Initial inspection pass — soffit baffles appear blocked. Need attic access for confirmation.', + photos: [ + { id: 'sct_005_a2_p1', url: '/assets/images/properties/Roof_Inspection_Angle.jpg', name: 'south-gable.jpg' }, + ], + actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor', + at: '2026-05-14T11:00:00Z', + }, + { + id: 'sct_005_a3', type: 'status_change', status: 'On Hold', + note: 'Work stopped — homeowner needs to clear stored boxes blocking attic access before I can finish.', + photos: [ + { id: 'sct_005_a3_p1', url: '/assets/images/properties/Beige_Two_Story_House.jpg', name: 'access-blocked.jpg' }, + ], + actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor', + at: '2026-05-15T16:00:00Z', + }, + ], projectId: 'proj_012', paymentStatus: 'Unpaid', paymentRequestedAt: '2026-05-14T13:00:00Z', @@ -5449,6 +5560,33 @@ const MOCK_SUBCONTRACTOR_TASKS = [ { id: 'sct_008_f1', description: 'Labour — Kitchen GFCI install (2 outlets + island)', type: 'Labour / Service Fee', amount: 220, createdAt: '2026-05-19T12:35:00Z' }, { id: 'sct_008_f2', description: 'Mobilization to site', type: 'Mobilization Fee', amount: 75, createdAt: '2026-05-19T08:00:00Z' }, ], + activities: [ + { + id: 'sct_008_a1', type: 'status_change', status: 'Assigned', + note: 'Task created and assigned to Carlos. Insurance covering code upgrade.', + photos: [], + actorId: 'ADM01', actorName: 'Admin One', actorRole: 'Admin', + at: '2026-05-16T13:00:00Z', + }, + { + id: 'sct_008_a2', type: 'status_change', status: 'In Progress', + note: 'Arrived on site. Verified wiring is copper (no aluminum). Starting kitchen counter outlets first.', + photos: [ + { id: 'sct_008_a2_p1', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', name: 'kitchen-before.jpg' }, + ], + actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor', + at: '2026-05-19T10:15:00Z', + }, + { + id: 'sct_008_a3', type: 'progress_update', status: 'In Progress', + note: 'Both kitchen counter outlets swapped to Leviton 20A GFCI. Tested with plug-in tester — passed.', + photos: [ + { id: 'sct_008_a3_p1', url: '/assets/images/properties/Sunny_Suburban_House.jpg', name: 'gfci-installed.jpg' }, + ], + actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor', + at: '2026-05-19T12:30:00Z', + }, + ], projectId: 'proj_011', paymentStatus: 'Unpaid', paymentRequestedAt: '2026-05-19T12:35:00Z', @@ -5456,6 +5594,43 @@ const MOCK_SUBCONTRACTOR_TASKS = [ updatedAt: '2026-05-19T18:00:00Z', }, + // ------------------------------------------------------------------- + // sct_010 — Carlos (sub_001) — newly assigned, awaiting acknowledgement + // ------------------------------------------------------------------- + { + id: 'sct_010', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_001', + subcontractorName: 'Carlos Subcontractor', + assignedBy: 'owner_001', + assignedByName: 'Justin Johnson', + title: 'Install EV charger circuit — garage', + location: '3917 Arizona Pl, Plano, TX 75023', + description: 'Homeowner purchased a Tesla Wall Connector. Run a dedicated 60A 240V circuit from the main panel to the garage wall. Coordinate with homeowner for outage window. Permit required.', + dueDate: '2026-05-29', + priority: 'medium', + status: 'Assigned', + photos: [ + { id: 'sct_010_p1', name: 'Garage_Wall.jpg', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', annotation: 'Wall Connector mounting location' }, + { id: 'sct_010_p2', name: 'Panel_Location.jpg', url: '/assets/images/properties/Brick_Front_Porch_Home.jpg', annotation: 'Main panel — check available breaker slots' }, + ], + statusHistory: [ + { id: 'sct_010_h1', status: 'Assigned', actorId: 'owner_001', actorName: 'Justin Johnson', comment: 'Task created and assigned. Please confirm ETA.', at: '2026-05-20T09:00:00Z' }, + ], + thread: [ + { id: 'sct_010_t1', channel: 'external', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Carlos — homeowner is hoping to have this in before next weekend. Can you take a look at the panel and confirm slot availability?', at: '2026-05-20T09:02:00Z' }, + ], + expenses: [], + fees: [], + activities: [], + projectId: 'proj_011', + paymentStatus: 'Unpaid', + paymentRequestedAt: null, + createdAt: '2026-05-20T09:00:00Z', + updatedAt: '2026-05-20T09:02:00Z', + }, + // ------------------------------------------------------------------- // sct_009 — Jennifer Wu (sub_004) — On Hold plumbing // ------------------------------------------------------------------- @@ -5952,13 +6127,19 @@ export const MockStoreProvider = ({ children }) => { return task; }; - const setSubcontractorTaskStatus = (taskId, nextStatus, comment, actor) => { + const setSubcontractorTaskStatus = (taskId, nextStatus, comment, actor, photos = []) => { const now = new Date().toISOString(); let didChange = false; let companyId = null; let companyName = null; let assignedByUserId = null; + const normalizedPhotos = (photos || []).map((p, i) => ({ + id: p.id || `${Date.now()}_${i}_${Math.random().toString(36).slice(2, 7)}`, + url: p.url, + name: p.name || '', + })); + setSubcontractorTasks(prev => prev.map(t => { if (t.id !== taskId) return t; if (t.status === nextStatus) return t; @@ -5974,10 +6155,22 @@ export const MockStoreProvider = ({ children }) => { comment: comment?.trim() || '', at: now, }; + const activityEntry = { + id: `a_${Date.now()}`, + type: 'status_change', + status: nextStatus, + note: comment?.trim() || '', + photos: normalizedPhotos, + actorId: actor?.id || t.subcontractorId, + actorName: actor?.name || t.subcontractorName, + actorRole: actor?.role || 'Subcontractor', + at: now, + }; return { ...t, status: nextStatus, statusHistory: [...(t.statusHistory || []), entry], + activities: [...(t.activities || []), activityEntry], updatedAt: now, }; })); @@ -6085,6 +6278,59 @@ export const MockStoreProvider = ({ children }) => { toast.success('Fee submitted'); }; + const addSubcontractorTaskActivity = (taskId, { note, photos = [] }, actor) => { + const trimmed = note?.trim() || ''; + if (!trimmed || !photos.length) return; + const now = new Date().toISOString(); + const normalizedPhotos = photos.map((p, i) => ({ + id: p.id || `${Date.now()}_${i}_${Math.random().toString(36).slice(2, 7)}`, + url: p.url, + name: p.name || '', + })); + let companyId = null; + let companyName = null; + let assignedByUserId = null; + let subId = null; + + setSubcontractorTasks(prev => prev.map(t => { + if (t.id !== taskId) return t; + companyId = t.companyId; + companyName = t.companyName; + assignedByUserId = t.assignedBy; + subId = t.subcontractorId; + const entry = { + id: `a_${Date.now()}`, + type: 'progress_update', + status: t.status, + note: trimmed, + photos: normalizedPhotos, + actorId: actor?.id || t.subcontractorId, + actorName: actor?.name || t.subcontractorName, + actorRole: actor?.role || 'Subcontractor', + at: now, + }; + return { + ...t, + activities: [...(t.activities || []), entry], + updatedAt: now, + }; + })); + + const recipientUserId = actor?.id === assignedByUserId ? subId : assignedByUserId; + if (recipientUserId) { + addNotification({ + recipientUserId, + recipientRole: actor?.id === assignedByUserId ? 'SUBCONTRACTOR' : 'OWNER', + type: 'progress_update', + message: `New progress update from ${actor?.name || 'subcontractor'}`, + taskId, + fromCompanyId: companyId, + fromCompanyName: companyName, + }); + } + toast.success('Progress update posted'); + }; + const updateSubcontractorTask = (taskId, data) => { let updatedSubId = null; let prevSubId = null; @@ -6421,6 +6667,7 @@ export const MockStoreProvider = ({ children }) => { addSubcontractorTaskMessage, addSubcontractorTaskExpense, addSubcontractorTaskFee, + addSubcontractorTaskActivity, notifications, addNotification, markNotificationRead, diff --git a/src/pages/subcontractor/SubcontractorTaskDetailPage.jsx b/src/pages/subcontractor/SubcontractorTaskDetailPage.jsx index 7bad063..85f532e 100644 --- a/src/pages/subcontractor/SubcontractorTaskDetailPage.jsx +++ b/src/pages/subcontractor/SubcontractorTaskDetailPage.jsx @@ -8,6 +8,7 @@ import { ArrowLeft, ChevronRight, ClipboardList, MapPin, Calendar, Building2, User, Hash, DollarSign, Receipt, Camera, Image as ImageIcon, MessageSquare, Lock, Send, Clock, PauseCircle, CheckCircle, RefreshCcw, AlertCircle, X, Plus, Upload, + Activity, ImagePlus, Loader2, Play, Trash2, AlertTriangle, } from 'lucide-react'; // --------------------------------------------------------------------------- @@ -102,6 +103,7 @@ const SubcontractorTaskDetailPage = () => { subcontractorTasks, subSelfStatuses, setSubcontractorTaskStatus, addSubcontractorTaskMessage, addSubcontractorTaskExpense, addSubcontractorTaskFee, + addSubcontractorTaskActivity, } = useMockStore(); const task = useMemo(() => subcontractorTasks.find(t => t.id === taskId), [subcontractorTasks, taskId]); @@ -109,6 +111,7 @@ const SubcontractorTaskDetailPage = () => { const [logExpenseOpen, setLogExpenseOpen] = useState(false); const [logFeeOpen, setLogFeeOpen] = useState(false); const [lightboxIndex, setLightboxIndex] = useState(null); + const [activityLightbox, setActivityLightbox] = useState(null); // { photos, index } // ESC closes any open modal — handled inside each modal too, but having a guard // at page level keeps focus tidy. @@ -116,10 +119,11 @@ const SubcontractorTaskDetailPage = () => { const handler = (e) => { if (e.key !== 'Escape') return; if (lightboxIndex !== null) setLightboxIndex(null); + if (activityLightbox) setActivityLightbox(null); }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); - }, [lightboxIndex]); + }, [lightboxIndex, activityLightbox]); if (!task) { return ( @@ -144,11 +148,19 @@ const SubcontractorTaskDetailPage = () => { const overdue = isOverdue(task); const closed = task.status === 'Completed' || task.status === 'Cancelled'; - const handleStatusChange = (nextStatus, comment) => { + const handleStatusChange = (nextStatus, comment, photos = []) => { setSubcontractorTaskStatus(task.id, nextStatus, comment, { id: user?.id || task.subcontractorId, name: user?.name || task.subcontractorName, role: 'Subcontractor', + }, photos); + }; + + const handlePostProgress = ({ note, photos }) => { + addSubcontractorTaskActivity(task.id, { note, photos }, { + id: user?.id || task.subcontractorId, + name: user?.name || task.subcontractorName, + role: 'Subcontractor', }); }; @@ -204,6 +216,8 @@ const SubcontractorTaskDetailPage = () => { disabled={closed} statuses={subSelfStatuses} onChange={handleStatusChange} + onPostProgress={handlePostProgress} + onActivityPreview={(photos, index) => setActivityLightbox({ photos, index })} /> { onClose={() => setLightboxIndex(null)} onIndexChange={setLightboxIndex} /> + setActivityLightbox(null)} + onIndexChange={(idx) => setActivityLightbox(prev => prev ? { ...prev, index: idx } : prev)} + /> ); }; @@ -323,36 +343,126 @@ const TaskHeaderCard = ({ task, overdue, onLogExpense, onLogFee }) => { }; // =========================================================================== -// Section 4 — Status Management +// Section 4 — Status Management (with note + photo evidence + progress updates) // =========================================================================== -const StatusManagementCard = ({ task, statuses, disabled, onChange }) => { - const [comment, setComment] = useState(''); +const STATUS_COPY = { + Assigned: { + title: 'Acknowledge assignment', + hint: 'Confirm you have received this task and attach a photo of the site or scope of work.', + notePlaceholder: 'e.g. Acknowledged — heading to site tomorrow morning.', + photoHint: 'Site or arrival photo', + accent: 'amber', + }, + 'In Progress': { + title: 'Start work', + hint: 'Add a brief note about what you are starting and one or more "before" photos.', + notePlaceholder: 'e.g. On site. Materials staged. Starting south slope ridge cap removal.', + photoHint: 'Before / starting photo', + accent: 'blue', + }, + 'On Hold': { + title: 'Put task on hold', + hint: 'Tell us why work has stopped, and attach a supporting photo (weather, materials, access, etc.).', + notePlaceholder: 'e.g. Work stopped due to rain. Will resume tomorrow.', + photoHint: 'Supporting photo (e.g. rain, blocked access)', + accent: 'orange', + }, + Completed: { + title: 'Mark as completed', + hint: 'Add a final completion note and at least one photo showing the finished work.', + notePlaceholder: 'e.g. House painting completed successfully. Walked through with homeowner.', + photoHint: 'Completion / final-look photo(s)', + accent: 'emerald', + }, +}; + +const ACCENT_BTN = { + amber: 'bg-amber-600 hover:bg-amber-500 shadow-amber-500/20', + blue: 'bg-blue-600 hover:bg-blue-500 shadow-blue-500/20', + orange: 'bg-orange-600 hover:bg-orange-500 shadow-orange-500/20', + emerald: 'bg-emerald-600 hover:bg-emerald-500 shadow-emerald-500/20', +}; + +const StatusManagementCard = ({ task, statuses, disabled, onChange, onPostProgress, onActivityPreview }) => { const [pendingStatus, setPendingStatus] = useState(null); + const [statusNote, setStatusNote] = useState(''); + const [statusPhotos, setStatusPhotos] = useState([]); + const [isSubmittingStatus, setIsSubmittingStatus] = useState(false); + + const [progressNote, setProgressNote] = useState(''); + const [progressPhotos, setProgressPhotos] = useState([]); + const [isSubmittingProgress, setIsSubmittingProgress] = useState(false); + + const copy = pendingStatus ? STATUS_COPY[pendingStatus] || STATUS_COPY.Assigned : null; + const accentBtnCls = copy ? ACCENT_BTN[copy.accent] : ACCENT_BTN.blue; + const noteOk = statusNote.trim().length > 0; + const photoOk = statusPhotos.length > 0; + const canConfirm = noteOk && photoOk && !isSubmittingStatus; + + const progressNoteOk = progressNote.trim().length > 0; + const progressPhotoOk = progressPhotos.length > 0; + const canPostProgress = progressNoteOk && progressPhotoOk && !isSubmittingProgress; const handlePick = (s) => { if (disabled) return; if (s === task.status) return; + if (isSubmittingStatus) return; setPendingStatus(s); + setStatusNote(''); + setStatusPhotos([]); }; - const confirm = () => { - if (!pendingStatus) return; - onChange(pendingStatus, comment); - setComment(''); - setPendingStatus(null); + const confirmStatus = () => { + if (!pendingStatus || !canConfirm) return; + setIsSubmittingStatus(true); + // Brief simulated commit so users see the loading state on slow devices. + setTimeout(() => { + onChange(pendingStatus, statusNote.trim(), statusPhotos); + setIsSubmittingStatus(false); + setStatusNote(''); + setStatusPhotos([]); + setPendingStatus(null); + }, 350); }; - const cancel = () => { + const cancelStatus = () => { + if (isSubmittingStatus) return; setPendingStatus(null); - setComment(''); + setStatusNote(''); + setStatusPhotos([]); }; + const submitProgress = () => { + if (!canPostProgress) return; + setIsSubmittingProgress(true); + setTimeout(() => { + onPostProgress({ note: progressNote.trim(), photos: progressPhotos }); + setIsSubmittingProgress(false); + setProgressNote(''); + setProgressPhotos([]); + }, 350); + }; + + const canPostUpdates = task.status === 'In Progress' || task.status === 'On Hold'; + const updateAccent = task.status === 'On Hold' ? 'orange' : 'blue'; + const updateBadgeCls = task.status === 'On Hold' + ? 'bg-orange-100 text-orange-700 dark:bg-orange-500/20 dark:text-orange-400' + : 'bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400'; + const UpdateIcon = task.status === 'On Hold' ? PauseCircle : Play; + const postBtnCls = task.status === 'On Hold' + ? 'bg-orange-600 hover:bg-orange-500 shadow-orange-500/20' + : 'bg-blue-600 hover:bg-blue-500 shadow-blue-500/20'; + + const sortedActivities = useMemo(() => { + return [...(task.activities || [])].sort((a, b) => new Date(b.at) - new Date(a.at)); + }, [task.activities]); + return (
@@ -366,7 +476,7 @@ const StatusManagementCard = ({ task, statuses, disabled, onChange }) => { key={s} type="button" onClick={() => handlePick(s)} - disabled={disabled} + disabled={disabled || isSubmittingStatus} className={`group relative flex items-center justify-center gap-2 px-3 py-3 rounded-xl text-sm font-bold border transition-all ${isCurrent ? 'border-blue-500 bg-blue-500/10 text-blue-600 dark:text-blue-400 shadow-lg shadow-blue-500/10' @@ -384,71 +494,358 @@ const StatusManagementCard = ({ task, statuses, disabled, onChange }) => { })}
- {pendingStatus && ( -
-

- Changing status to {pendingStatus}. Add an optional comment for the admin. -

-