216 lines
7.9 KiB
TypeScript
216 lines
7.9 KiB
TypeScript
import CheckSquareIcon from './icons/CheckSquareIcon';
|
|
import Trash2Icon from './icons/Trash2Icon';
|
|
import Edit2Icon from './icons/Edit2Icon';
|
|
import PlusIcon from './icons/PlusIcon';
|
|
import { useModal } from '../context/modals/useModal';
|
|
import { CreateChecklistModal } from './CreateChecklistModal';
|
|
import { DeleteChecklistModal } from './DeleteChecklistModal';
|
|
import { EditCheckItemModal } from './EditCheckItemModal';
|
|
import { CheckItem } from '../types/kanban';
|
|
|
|
interface CardChecklistsProps {
|
|
checklists: any[];
|
|
cardId: number;
|
|
addChecklist: (name: string, pos: number) => Promise<boolean>;
|
|
removeChecklist: (id: number) => Promise<boolean>;
|
|
addCheckItem: (
|
|
checklistId: number,
|
|
name: string,
|
|
pos: number,
|
|
state?: 'incomplete' | 'complete'
|
|
) => Promise<boolean>;
|
|
toggleCheckItem: (item: CheckItem, currentState: 'incomplete' | 'complete') => Promise<boolean>;
|
|
editCheckItem: (
|
|
itemId: number,
|
|
name: string,
|
|
pos: number,
|
|
state: 'incomplete' | 'complete'
|
|
) => Promise<boolean>;
|
|
removeCheckItem: (itemId: number) => Promise<boolean>;
|
|
}
|
|
|
|
export function CardChecklists({
|
|
checklists,
|
|
addChecklist,
|
|
removeChecklist,
|
|
addCheckItem,
|
|
toggleCheckItem,
|
|
editCheckItem,
|
|
removeCheckItem,
|
|
}: CardChecklistsProps) {
|
|
const { openModal } = useModal();
|
|
|
|
const handleAddChecklist = () => {
|
|
openModal((props) => (
|
|
<CreateChecklistModal
|
|
onSave={async (name) => {
|
|
const checklistCount = checklists?.length || 0;
|
|
return await addChecklist(name, checklistCount);
|
|
}}
|
|
onClose={props.onClose}
|
|
/>
|
|
));
|
|
};
|
|
|
|
const handleDeleteChecklist = (checklist: any) => {
|
|
openModal((props) => (
|
|
<DeleteChecklistModal
|
|
checklistName={checklist.name}
|
|
onDelete={async () => {
|
|
const success = await removeChecklist(checklist.id);
|
|
if (success) {
|
|
props.onClose();
|
|
}
|
|
}}
|
|
onClose={props.onClose}
|
|
/>
|
|
));
|
|
};
|
|
|
|
const handleAddCheckItem = (checklist: any) => {
|
|
const itemName = prompt('Enter item name:');
|
|
if (itemName && itemName.trim()) {
|
|
const itemCount = checklist.items?.length || 0;
|
|
addCheckItem(checklist.id, itemName.trim(), itemCount);
|
|
}
|
|
};
|
|
|
|
const handleToggleCheckItem = async (item: any) => {
|
|
await toggleCheckItem(item, item.state);
|
|
};
|
|
|
|
const handleEditCheckItem = (item: any) => {
|
|
openModal((props) => (
|
|
<EditCheckItemModal
|
|
itemName={item.name}
|
|
onSave={async (name) => {
|
|
return await editCheckItem(item.id, name, item.pos, item.state);
|
|
}}
|
|
onClose={props.onClose}
|
|
/>
|
|
));
|
|
};
|
|
|
|
const handleDeleteCheckItem = (item: any) => {
|
|
if (confirm('Are you sure you want to delete this item?')) {
|
|
removeCheckItem(item.id);
|
|
}
|
|
};
|
|
return (
|
|
<div className="bg-gray-800 rounded-lg p-6">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h2 className="text-xl font-bold text-white flex items-center gap-2">
|
|
<span className="w-5 h-5">
|
|
<CheckSquareIcon />
|
|
</span>
|
|
Checklists
|
|
</h2>
|
|
<button
|
|
onClick={handleAddChecklist}
|
|
className="bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-1.5 px-3 rounded-lg transition-colors flex items-center gap-1"
|
|
>
|
|
<span className="w-4 h-4">
|
|
<PlusIcon />
|
|
</span>
|
|
Add Checklist
|
|
</button>
|
|
</div>
|
|
{checklists && checklists.length > 0 ? (
|
|
<div className="space-y-4">
|
|
{checklists.map((checklist: any) => {
|
|
const completedCount =
|
|
checklist.items?.filter((item: any) => item.state === 'complete').length || 0;
|
|
const totalCount = checklist.items?.length || 0;
|
|
const progress = totalCount > 0 ? (completedCount / totalCount) * 100 : 0;
|
|
|
|
return (
|
|
<div key={checklist.id} className="border border-gray-700 rounded-lg p-4">
|
|
<div className="flex justify-between items-start mb-3">
|
|
<div className="flex-1">
|
|
<h3 className="font-semibold text-white mb-1">{checklist.name}</h3>
|
|
<div className="flex items-center gap-2">
|
|
<div className="flex-1 h-2 bg-gray-700 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-green-500 transition-all duration-300"
|
|
style={{ width: `${progress}%` }}
|
|
/>
|
|
</div>
|
|
<span className="text-xs text-gray-400 whitespace-nowrap">
|
|
{completedCount}/{totalCount}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => handleDeleteChecklist(checklist)}
|
|
className="text-gray-400 hover:text-red-400 transition-colors ml-2"
|
|
title="Delete checklist"
|
|
>
|
|
<span className="w-4 h-4">
|
|
<Trash2Icon />
|
|
</span>
|
|
</button>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{checklist.items && checklist.items.length > 0 ? (
|
|
checklist.items.map((item: any) => (
|
|
<div
|
|
key={item.id}
|
|
className="flex items-center gap-3 p-2 bg-gray-700 rounded group hover:bg-gray-600 transition-colors"
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
checked={item.state === 'complete'}
|
|
onChange={() => handleToggleCheckItem(item)}
|
|
className="w-5 h-5 rounded cursor-pointer"
|
|
/>
|
|
<span
|
|
onClick={() => handleToggleCheckItem(item)}
|
|
className={`flex-1 text-sm cursor-pointer ${item.state === 'complete' ? 'text-gray-400 line-through' : 'text-white'}`}
|
|
>
|
|
{item.name}
|
|
</span>
|
|
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<button
|
|
onClick={() => handleEditCheckItem(item)}
|
|
className="text-gray-400 hover:text-white transition-colors p-1"
|
|
title="Edit item"
|
|
>
|
|
<span className="w-3.5 h-3.5">
|
|
<Edit2Icon />
|
|
</span>
|
|
</button>
|
|
<button
|
|
onClick={() => handleDeleteCheckItem(item)}
|
|
className="text-gray-400 hover:text-red-400 transition-colors p-1"
|
|
title="Delete item"
|
|
>
|
|
<span className="w-3.5 h-3.5">
|
|
<Trash2Icon />
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
<p className="text-gray-400 text-sm">No items yet</p>
|
|
)}
|
|
<button
|
|
onClick={() => handleAddCheckItem(checklist)}
|
|
className="text-blue-400 hover:text-blue-300 text-sm font-medium mt-2 flex items-center gap-1"
|
|
>
|
|
<span className="w-4 h-4">
|
|
<PlusIcon />
|
|
</span>
|
|
Add an item
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
) : (
|
|
<p className="text-gray-400 text-sm">No checklists yet. Add one to get started!</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|