RFP_Space_V05 / components /InteractiveChecklist.tsx
Varun10000's picture
Upload 89 files
17cf14d verified
Raw
History Blame Contribute Delete
7.99 kB
'use client'
import { useState } from 'react'
interface ChecklistItemProps {
id: string
itemName: string
status: string
owner: { id: string; name: string } | null
phase: string
onUpdate: () => void
}
export function InteractiveChecklistItem({
id,
itemName,
status,
owner,
phase,
onUpdate,
}: ChecklistItemProps) {
const [isEditing, setIsEditing] = useState(false)
const [editName, setEditName] = useState(itemName)
const [isUpdating, setIsUpdating] = useState(false)
const handleStatusChange = async (newStatus: string) => {
setIsUpdating(true)
try {
const response = await fetch(`/api/checklist/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: newStatus }),
})
if (response.ok) {
onUpdate()
}
} catch (error) {
console.error('Failed to update status:', error)
} finally {
setIsUpdating(false)
}
}
const handleNameUpdate = async () => {
if (editName.trim() === itemName) {
setIsEditing(false)
return
}
setIsUpdating(true)
try {
const response = await fetch(`/api/checklist/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ itemName: editName.trim() }),
})
if (response.ok) {
setIsEditing(false)
onUpdate()
}
} catch (error) {
console.error('Failed to update name:', error)
} finally {
setIsUpdating(false)
}
}
const handleDelete = async () => {
if (!confirm('Delete this checklist item?')) return
setIsUpdating(true)
try {
const response = await fetch(`/api/checklist/${id}`, {
method: 'DELETE',
})
if (response.ok) {
onUpdate()
}
} catch (error) {
console.error('Failed to delete item:', error)
} finally {
setIsUpdating(false)
}
}
const statusOptions = ['Not Started', 'In Progress', 'Blocked', 'Complete']
return (
<div
className={`flex items-center gap-4 p-4 rounded-xl transition-all ${
status === 'Complete'
? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
: status === 'In Progress'
? 'bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200'
: status === 'Blocked'
? 'bg-gradient-to-r from-red-50 to-pink-50 border border-red-200'
: 'bg-white border border-gray-200'
} ${isUpdating ? 'opacity-50' : ''}`}
>
{/* Status Dropdown */}
<select
value={status}
onChange={(e) => handleStatusChange(e.target.value)}
disabled={isUpdating}
className="px-3 py-2 rounded-lg border-2 border-gray-300 focus:border-blue-500 focus:outline-none bg-white text-sm font-medium cursor-pointer hover:border-blue-400 transition-all"
>
{statusOptions.map((s) => (
<option key={s} value={s}>
{s === 'Not Started' ? '⚪' : s === 'In Progress' ? '🔵' : s === 'Blocked' ? '🔴' : '✅'} {s}
</option>
))}
</select>
{/* Item Name - Editable */}
<div className="flex-1">
{isEditing ? (
<div className="flex items-center gap-2">
<input
type="text"
value={editName}
onChange={(e) => setEditName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleNameUpdate()
if (e.key === 'Escape') {
setEditName(itemName)
setIsEditing(false)
}
}}
className="flex-1 px-3 py-2 rounded-lg border-2 border-blue-500 focus:outline-none"
autoFocus
/>
<button
onClick={handleNameUpdate}
className="px-3 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 text-sm"
>
Save
</button>
<button
onClick={() => {
setEditName(itemName)
setIsEditing(false)
}}
className="px-3 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 text-sm"
>
Cancel
</button>
</div>
) : (
<div
className="flex items-center gap-2 cursor-pointer group"
onClick={() => setIsEditing(true)}
>
<span className={`${status === 'Complete' ? 'line-through text-gray-500' : 'text-gray-900'}`}>
{itemName}
</span>
<span className="opacity-0 group-hover:opacity-100 text-xs text-gray-400">✏️ Click to edit</span>
</div>
)}
</div>
{/* Owner */}
<div className="text-sm text-gray-600 min-w-[120px]">
<span className="text-xs text-gray-500">Owner:</span> {owner?.name || 'Unassigned'}
</div>
{/* Delete Button */}
<button
onClick={handleDelete}
disabled={isUpdating}
className="p-2 text-red-500 hover:bg-red-50 rounded-lg transition-all"
title="Delete item"
>
🗑️
</button>
</div>
)
}
interface AddChecklistItemProps {
rfpId: string
phase: string
onAdd: () => void
}
export function AddChecklistItem({ rfpId, phase, onAdd }: AddChecklistItemProps) {
const [isAdding, setIsAdding] = useState(false)
const [newItemName, setNewItemName] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const handleAdd = async () => {
if (!newItemName.trim()) return
setIsSubmitting(true)
try {
const response = await fetch('/api/checklist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
rfpId,
itemName: newItemName.trim(),
phase,
status: 'Not Started',
}),
})
if (response.ok) {
setNewItemName('')
setIsAdding(false)
onAdd()
}
} catch (error) {
console.error('Failed to add item:', error)
} finally {
setIsSubmitting(false)
}
}
if (!isAdding) {
return (
<button
onClick={() => setIsAdding(true)}
className="w-full p-3 border-2 border-dashed border-gray-300 rounded-xl text-gray-600 hover:border-blue-400 hover:text-blue-600 hover:bg-blue-50/50 transition-all text-sm font-medium"
>
+ Add Custom Item to {phase}
</button>
)
}
return (
<div className="flex items-center gap-2 p-3 border-2 border-blue-300 rounded-xl bg-blue-50/50">
<input
type="text"
value={newItemName}
onChange={(e) => setNewItemName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleAdd()
if (e.key === 'Escape') {
setNewItemName('')
setIsAdding(false)
}
}}
placeholder="Enter checklist item name..."
className="flex-1 px-3 py-2 rounded-lg border-2 border-gray-300 focus:border-blue-500 focus:outline-none"
autoFocus
/>
<button
onClick={handleAdd}
disabled={isSubmitting || !newItemName.trim()}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 text-sm font-medium"
>
Add
</button>
<button
onClick={() => {
setNewItemName('')
setIsAdding(false)
}}
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 text-sm"
>
Cancel
</button>
</div>
)
}