kanban-app/frontend/src/components/CardChecklists.tsx
2026-02-27 20:34:44 +03:00

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>
);
}