diff --git a/docs/kanban_api.md b/docs/kanban_api.md deleted file mode 100644 index e85b04c..0000000 --- a/docs/kanban_api.md +++ /dev/null @@ -1,514 +0,0 @@ -# Kanban API Documentation - -This document describes all the API endpoints for the Kanban application. All endpoints require authentication unless otherwise noted. - -## Base URL -``` -http://localhost:5000/api -``` - -## Authentication - -All endpoints (except register and login) require a JWT token in the Authorization header: -``` -Authorization: Bearer -``` - -## Boards - -### Get All Boards -``` -GET /api/boards -``` -Get all boards for the current authenticated user. - -**Response:** Array of board objects - -### Get Single Board -``` -GET /api/boards/ -``` -Get a board with all its details including lists, cards, and labels. - -**Response:** Board object with nested lists, cards, and labels - -### Create Board -``` -POST /api/boards -``` -Create a new board. - -**Request Body:** -```json -{ - "name": "My Project Board", - "description": "Project management board", - "url": "https://example.com/board/123", - "short_link": "abc123", - "short_url": "https://example.com/b/abc123", - "prefs": {}, - "label_names": {}, - "limits": {} -} -``` - -**Response:** Created board object - -### Update Board -``` -PUT /api/boards/ -``` -Update a board. - -**Request Body:** Partial board object (only include fields to update) - -**Response:** Updated board object - -### Delete Board -``` -DELETE /api/boards/ -``` -Delete a board and all its associated data. - -**Response:** Success message - -## Lists - -### Create List -``` -POST /api/boards//lists -``` -Create a new list in a board. - -**Request Body:** -```json -{ - "name": "To Do", - "pos": 0 -} -``` - -**Response:** Created list object - -### Update List -``` -PUT /api/lists/ -``` -Update a list. - -**Request Body:** Partial list object - -**Response:** Updated list object - -### Delete List -``` -DELETE /api/lists/ -``` -Delete a list and all its cards. - -**Response:** Success message - -## Cards - -### Create Card -``` -POST /api/lists//cards -``` -Create a new card in a list. - -**Request Body:** -```json -{ - "name": "Task Name", - "description": "Task description", - "pos": 0, - "due": "2026-12-31T23:59:59Z", - "due_complete": false, - "badges": {}, - "cover": {}, - "desc_data": {} -} -``` - -**Response:** Created card object - -### Get Single Card -``` -GET /api/cards/ -``` -Get a card with full details including labels, checklists, and comments. - -**Response:** Card object with nested labels, checklists, items, and comments - -### Update Card -``` -PUT /api/cards/ -``` -Update a card. Can also move card to different list by providing `list_id`. - -**Request Body:** Partial card object - -**Response:** Updated card object - -### Delete Card -``` -DELETE /api/cards/ -``` -Delete a card and all its associated data. - -**Response:** Success message - -## Labels - -### Get Board Labels -``` -GET /api/boards//labels -``` -Get all labels for a board. - -**Response:** Array of label objects - -### Create Label -``` -POST /api/boards//labels -``` -Create a new label in a board. - -**Request Body:** -```json -{ - "name": "Urgent", - "color": "red" -} -``` - -**Response:** Created label object - -### Add Label to Card -``` -POST /api/cards//labels -``` -Add a label to a card. - -**Request Body:** -```json -{ - "label_id": 1 -} -``` - -**Response:** Created card-label association object - -### Remove Label from Card -``` -DELETE /api/cards//labels/ -``` -Remove a label from a card. - -**Response:** Success message - -## Checklists - -### Create Checklist -``` -POST /api/cards//checklists -``` -Create a new checklist in a card. - -**Request Body:** -```json -{ - "name": "Tasks", - "pos": 0 -} -``` - -**Response:** Created checklist object - -### Create Check Item -``` -POST /api/checklists//items -``` -Create a new check item in a checklist. - -**Request Body:** -```json -{ - "name": "Complete task", - "pos": 0, - "state": "incomplete", - "due": "2026-12-31T23:59:59Z" -} -``` - -**Response:** Created check item object - -### Update Check Item -``` -PUT /api/check-items/ -``` -Update a check item (typically used to toggle state). - -**Request Body:** Partial check item object - -**Response:** Updated check item object - -### Delete Checklist -``` -DELETE /api/checklists/ -``` -Delete a checklist and all its items. - -**Response:** Success message - -### Delete Check Item -``` -DELETE /api/check-items/ -``` -Delete a check item. - -**Response:** Success message - -## Comments - -### Get Card Comments -``` -GET /api/cards//comments -``` -Get all comments for a card, ordered by creation date (newest first). - -**Response:** Array of comment objects with user information - -### Create Comment -``` -POST /api/cards//comments -``` -Create a new comment on a card. - -**Request Body:** -```json -{ - "text": "This is a comment" -} -``` - -**Response:** Created comment object - -### Update Comment -``` -PUT /api/comments/ -``` -Update a comment (only by the comment author). - -**Request Body:** Partial comment object - -**Response:** Updated comment object - -### Delete Comment -``` -DELETE /api/comments/ -``` -Delete a comment (only by the comment author). - -**Response:** Success message - -## Data Models - -### Board -```json -{ - "id": 1, - "name": "My Board", - "description": "Board description", - "closed": false, - "url": "https://example.com", - "short_link": "abc123", - "short_url": "https://example.com/b/abc123", - "user_id": 1, - "date_last_activity": "2026-01-01T00:00:00Z", - "date_last_view": "2026-01-01T00:00:00Z", - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z", - "prefs": {}, - "label_names": {}, - "limits": {} -} -``` - -### List -```json -{ - "id": 1, - "name": "To Do", - "closed": false, - "pos": 0, - "board_id": 1, - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z" -} -``` - -### Card -```json -{ - "id": 1, - "name": "Task Name", - "description": "Task description", - "closed": false, - "due": "2026-12-31T23:59:59Z", - "due_complete": false, - "pos": 0, - "id_short": 1, - "board_id": 1, - "list_id": 1, - "date_last_activity": "2026-01-01T00:00:00Z", - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z", - "badges": {}, - "cover": {}, - "desc_data": {}, - "labels": [], - "checklists": [], - "comments": [] -} -``` - -### Label -```json -{ - "id": 1, - "name": "Urgent", - "color": "red", - "uses": 0, - "board_id": 1, - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z" -} -``` - -### Checklist -```json -{ - "id": 1, - "name": "Tasks", - "pos": 0, - "board_id": 1, - "card_id": 1, - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z", - "items": [] -} -``` - -### Check Item -```json -{ - "id": 1, - "name": "Complete task", - "pos": 0, - "state": "incomplete", - "due": "2026-12-31T23:59:59Z", - "checklist_id": 1, - "user_id": 1, - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z" -} -``` - -### Comment -```json -{ - "id": 1, - "text": "Comment text", - "card_id": 1, - "user_id": 1, - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z", - "user": {} -} -``` - -## Common Response Codes - -- `200 OK` - Request successful -- `201 Created` - Resource created successfully -- `400 Bad Request` - Invalid request data -- `401 Unauthorized` - Authentication required or invalid -- `403 Forbidden` - Access denied (not your resource) -- `404 Not Found` - Resource not found -- `500 Internal Server Error` - Server error - -## Usage Examples - -### Creating a complete board with lists and cards - -```bash -# 1. Login to get token -curl -X POST http://localhost:5000/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email": "user@example.com", "password": "password"}' - -# 2. Create a board -curl -X POST http://localhost:5000/api/boards \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"name": "My Project"}' - -# 3. Create a list -curl -X POST http://localhost:5000/api/boards/1/lists \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"name": "To Do", "pos": 0}' - -# 4. Create a card -curl -X POST http://localhost:5000/api/lists/1/cards \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"name": "Task 1", "pos": 0}' -``` - -### Adding labels to a card - -```bash -# 1. Create a label -curl -X POST http://localhost:5000/api/boards/1/labels \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"name": "Urgent", "color": "red"}' - -# 2. Add label to card -curl -X POST http://localhost:5000/api/cards/1/labels \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"label_id": 1}' -``` - -### Creating a checklist with items - -```bash -# 1. Create checklist -curl -X POST http://localhost:5000/api/cards/1/checklists \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"name": "Subtasks", "pos": 0}' - -# 2. Add check items -curl -X POST http://localhost:5000/api/checklists/1/items \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"name": "Task 1", "pos": 0}' - -# 3. Mark item as complete -curl -X PUT http://localhost:5000/api/check-items/1 \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"state": "complete"}' -``` - -### Adding comments - -```bash -curl -X POST http://localhost:5000/api/cards/1/comments \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"text": "This is a comment"}' \ No newline at end of file diff --git a/docs/kanban_implementation_summary.md b/docs/kanban_implementation_summary.md deleted file mode 100644 index d8f9d93..0000000 --- a/docs/kanban_implementation_summary.md +++ /dev/null @@ -1,300 +0,0 @@ -# Kanban Backend Implementation Summary - -## Overview -This document summarizes the Kanban backend implementation for the project. The backend provides a complete API for managing Kanban boards, lists, cards, labels, checklists, and comments. - -## Database Schema - -### Tables Created - -1. **boards** - Main board entities - - id, name, description, closed, url, short_link, short_url - - user_id (foreign key to users) - - date_last_activity, date_last_view, created_at, updated_at - - JSONB fields: prefs, label_names, limits - - Relationships: lists, cards, labels - -2. **lists** - Board columns/lists - - id, name, closed, pos - - board_id (foreign key to boards) - - created_at, updated_at - - Relationships: cards - -3. **cards** - Task cards within lists - - id, name, description, closed, due, due_complete, pos, id_short - - board_id (foreign key to boards), list_id (foreign key to lists) - - date_last_activity, created_at, updated_at - - JSONB fields: badges, cover, desc_data - - Relationships: checklists, labels, comments - -4. **labels** - Color-coded tags - - id, name, color, uses - - board_id (foreign key to boards) - - created_at, updated_at - - Relationships: card_labels - -5. **card_labels** - Many-to-many relationship between cards and labels - - id, card_id, label_id - - Unique constraint on (card_id, label_id) - -6. **checklists** - Checklists within cards - - id, name, pos - - board_id (foreign key to boards), card_id (foreign key to cards) - - created_at, updated_at - - Relationships: check_items - -7. **check_items** - Items within checklists - - id, name, pos, state, due - - checklist_id (foreign key to checklists), user_id (foreign key to users) - - created_at, updated_at - -8. **comments** - Comments on cards - - id, text - - card_id (foreign key to cards), user_id (foreign key to users) - - created_at, updated_at - -## API Endpoints - -### Boards (`/api/boards`) -- `GET /api/boards` - Get all boards for current user -- `GET /api/boards/` - Get board with full details (lists, cards, labels) -- `POST /api/boards` - Create new board -- `PUT /api/boards/` - Update board -- `DELETE /api/boards/` - Delete board - -### Lists (`/api/lists`, `/api/boards//lists`) -- `POST /api/boards//lists` - Create list in board -- `PUT /api/lists/` - Update list -- `DELETE /api/lists/` - Delete list - -### Cards (`/api/cards`, `/api/lists//cards`) -- `POST /api/lists//cards` - Create card in list -- `GET /api/cards/` - Get card with full details -- `PUT /api/cards/` - Update card (includes moving between lists) -- `DELETE /api/cards/` - Delete card - -### Labels (`/api/boards//labels`, `/api/cards//labels`) -- `GET /api/boards//labels` - Get all labels for board -- `POST /api/boards//labels` - Create label -- `POST /api/cards//labels` - Add label to card -- `DELETE /api/cards//labels/` - Remove label from card - -### Checklists (`/api/checklists`, `/api/cards//checklists`) -- `POST /api/cards//checklists` - Create checklist -- `DELETE /api/checklists/` - Delete checklist - -### Check Items (`/api/check-items`, `/api/checklists//items`) -- `POST /api/checklists//items` - Create check item -- `PUT /api/check-items/` - Update check item -- `DELETE /api/check-items/` - Delete check item - -### Comments (`/api/comments`, `/api/cards//comments`) -- `GET /api/cards//comments` - Get all comments for card -- `POST /api/cards//comments` - Create comment -- `PUT /api/comments/` - Update comment -- `DELETE /api/comments/` - Delete comment - -## Key Features - -### Authentication -- All endpoints (except register/login) require JWT authentication -- User can only access their own boards and related resources -- Comments can only be edited/deleted by their author - -### Data Integrity -- Cascading deletes ensure cleanup of related data -- Foreign key constraints maintain referential integrity -- Unique constraints prevent duplicate card-label associations - -### Position Tracking -- All orderable entities (lists, cards, checklists, check items) have `pos` field -- Enables flexible sorting and drag-and-drop functionality - -### Activity Tracking -- Boards track `date_last_activity` and `date_last_view` -- Cards track `date_last_activity` -- Useful for showing recent activity - -### JSONB Fields -- Flexible storage for complex data (prefs, limits, badges, cover, desc_data) -- Allows extensibility without schema changes -- Supports Trello-like feature parity - -## File Structure - -``` -backend/app/ -├── models/ -│ ├── board.py # Board model -│ ├── list_model.py # List model (named to avoid Python conflict) -│ ├── card.py # Card model -│ ├── label.py # Label model -│ ├── card_label.py # Card-Label junction table -│ ├── checklist.py # Checklist model -│ ├── check_item.py # CheckItem model -│ ├── comment.py # Comment model -│ └── user.py # Updated with boards relationship -├── routes/ -│ └── kanban.py # All Kanban API routes -└── __init__.py # Updated to import models and register blueprint - -docs/ -├── kanban_api.md # Complete API documentation -└── kanban_implementation_summary.md # This file -``` - -## Migration - -Migration file: `backend/migrations/versions/1c0b9dfbd933_add_kanban_models_board_list_card_label_.py` - -To apply migrations: -```bash -cd backend && . venv/bin/activate && flask db upgrade -``` - -## Usage Flow - -### Typical User Workflow - -1. **Register/Login** - - User registers account or logs in - - Receives JWT token for authentication - -2. **Create Board** - - User creates a new board - - Board is associated with their user ID - -3. **Add Lists** - - User adds lists (columns) to the board - - Examples: "To Do", "In Progress", "Done" - -4. **Add Cards** - - User creates cards within lists - - Cards can have descriptions, due dates, etc. - -5. **Enhance Cards** - - Add labels for categorization - - Add checklists for subtasks - - Add comments for collaboration - -6. **Manage Work** - - Move cards between lists (drag-and-drop) - - Update card details - - Mark checklist items as complete - - Delete completed items - -## Design Decisions - -### Single User App -- No organization or membership models (as specified) -- Each board belongs to exactly one user -- Simplifies permissions model - -### Trello-Inspired Schema -- Uses similar field names and structure as Trello -- Makes it familiar to users -- Supports importing from Trello JSON exports - -### Position-Based Ordering -- Uses float `pos` field for ordering -- Allows inserting items between others -- Supports infinite granularity for drag-and-drop - -### Cascading Deletes -- Deleting a board deletes all its lists, cards, labels, etc. -- Deleting a list deletes all its cards -- Ensures no orphaned data - -### JSONB for Flexible Data -- Stores complex nested data without normalized tables -- Simplifies schema for optional features -- Maintains flexibility for future enhancements - -## Next Steps - -### Backend -- [ ] Add validation schemas (Pydantic) -- [ ] Add comprehensive tests -- [ ] Add rate limiting -- [ ] Add file upload support for attachments -- [ ] Add activity logging/history -- [ ] Add search functionality -- [ ] Add filtering and sorting options - -### Frontend -- [ ] Create board list view -- [ ] Create board detail view with drag-and-drop -- [ ] Implement card creation/editing -- [ ] Implement label management -- [ ] Implement checklist functionality -- [ ] Implement comments -- [ ] Add real-time updates (WebSocket) - -## Testing - -Test the API using the examples in `docs/kanban_api.md` or use tools like: -- Postman -- Insomnia -- curl (command line) - -Example: -```bash -# Start the backend server -make dev-services # Start postgres & redis -make dev-backend # Start Flask server - -# Test endpoints -curl http://localhost:5000/api/health -``` - -## Security Considerations - -- JWT authentication required for all operations -- User isolation: users can only access their own resources -- SQL injection prevention through SQLAlchemy ORM -- Input validation on all endpoints -- CORS configured for frontend integration - -## Performance Notes - -- Indexed fields: user_id, board_id, list_id, card_id, etc. -- Lazy loading relationships to avoid N+1 queries -- Efficient queries using SQLAlchemy's query builder -- JSONB fields use PostgreSQL's optimized JSON storage - -## Troubleshooting - -### Migration Issues -```bash -# Reset migrations (WARNING: deletes data) -cd backend && . venv/bin/activate && flask db downgrade base -rm -rf migrations/versions/*.py -flask db migrate -m "Initial migration" -flask db upgrade -``` - -### Database Connection Issues -```bash -# Restart services -make dev-stop-services -make dev-services -``` - -### Import Errors -```bash -# Ensure venv is activated -cd backend && . venv/bin/activate -pip install -r requirements/dev.txt -``` - -## Summary - -The Kanban backend is now fully implemented with: -- ✅ Complete database schema -- ✅ RESTful API endpoints -- ✅ Authentication and authorization -- ✅ Data integrity and validation -- ✅ Comprehensive documentation -- ✅ Database migration - -The backend is ready for frontend integration and testing. \ No newline at end of file diff --git a/frontend/src/components/CardChecklists.tsx b/frontend/src/components/CardChecklists.tsx new file mode 100644 index 0000000..aee5601 --- /dev/null +++ b/frontend/src/components/CardChecklists.tsx @@ -0,0 +1,216 @@ +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; + removeChecklist: (id: number) => Promise; + addCheckItem: ( + checklistId: number, + name: string, + pos: number, + state?: 'incomplete' | 'complete' + ) => Promise; + toggleCheckItem: (item: CheckItem, currentState: 'incomplete' | 'complete') => Promise; + editCheckItem: ( + itemId: number, + name: string, + pos: number, + state: 'incomplete' | 'complete' + ) => Promise; + removeCheckItem: (itemId: number) => Promise; +} + +export function CardChecklists({ + checklists, + addChecklist, + removeChecklist, + addCheckItem, + toggleCheckItem, + editCheckItem, + removeCheckItem, +}: CardChecklistsProps) { + const { openModal } = useModal(); + + const handleAddChecklist = () => { + openModal((props) => ( + { + const checklistCount = checklists?.length || 0; + return await addChecklist(name, checklistCount); + }} + onClose={props.onClose} + /> + )); + }; + + const handleDeleteChecklist = (checklist: any) => { + openModal((props) => ( + { + 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) => ( + { + 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 ( +
+
+

+ + + + Checklists +

+ +
+ {checklists && checklists.length > 0 ? ( +
+ {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 ( +
+
+
+

{checklist.name}

+
+
+
+
+ + {completedCount}/{totalCount} + +
+
+ +
+
+ {checklist.items && checklist.items.length > 0 ? ( + checklist.items.map((item: any) => ( +
+ handleToggleCheckItem(item)} + className="w-5 h-5 rounded cursor-pointer" + /> + handleToggleCheckItem(item)} + className={`flex-1 text-sm cursor-pointer ${item.state === 'complete' ? 'text-gray-400 line-through' : 'text-white'}`} + > + {item.name} + +
+ + +
+
+ )) + ) : ( +

No items yet

+ )} + +
+
+ ); + })} +
+ ) : ( +

No checklists yet. Add one to get started!

+ )} +
+ ); +} diff --git a/frontend/src/components/CreateChecklistModal.tsx b/frontend/src/components/CreateChecklistModal.tsx new file mode 100644 index 0000000..bbb66b3 --- /dev/null +++ b/frontend/src/components/CreateChecklistModal.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react'; + +interface CreateChecklistModalProps { + onSave: (name: string) => Promise; + onClose: () => void; +} + +export function CreateChecklistModal({ onSave, onClose }: CreateChecklistModalProps) { + const [name, setName] = useState(''); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!name.trim()) return; + + const success = await onSave(name); + if (success) { + onClose(); + } + }; + + return ( +
+

Add Checklist

+
+
+ + setName(e.target.value)} + className="w-full bg-gray-700 text-white rounded-lg p-3 border border-gray-600 focus:border-blue-500 focus:outline-none" + placeholder="Checklist title..." + autoFocus + /> +
+
+ + +
+
+
+ ); +} diff --git a/frontend/src/components/DeleteChecklistModal.tsx b/frontend/src/components/DeleteChecklistModal.tsx new file mode 100644 index 0000000..8e153bf --- /dev/null +++ b/frontend/src/components/DeleteChecklistModal.tsx @@ -0,0 +1,45 @@ +import Trash2Icon from './icons/Trash2Icon'; + +interface DeleteChecklistModalProps { + checklistName: string; + onDelete: () => void; + onClose: () => void; +} + +export function DeleteChecklistModal({ + checklistName, + onDelete, + onClose, +}: DeleteChecklistModalProps) { + return ( +
+
+ + + + + +

Delete Checklist

+
+

+ Are you sure you want to delete + "{checklistName}"? This action + cannot be undone. +

+
+ + +
+
+ ); +} diff --git a/frontend/src/components/EditCheckItemModal.tsx b/frontend/src/components/EditCheckItemModal.tsx new file mode 100644 index 0000000..9e779e5 --- /dev/null +++ b/frontend/src/components/EditCheckItemModal.tsx @@ -0,0 +1,60 @@ +import { useState, useEffect } from 'react'; + +interface EditCheckItemModalProps { + itemName: string; + onSave: (name: string) => Promise; + onClose: () => void; +} + +export function EditCheckItemModal({ itemName, onSave, onClose }: EditCheckItemModalProps) { + const [name, setName] = useState(itemName); + + useEffect(() => { + setName(itemName); + }, [itemName]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!name.trim()) return; + + const success = await onSave(name); + if (success) { + onClose(); + } + }; + + return ( +
+

Edit Item

+
+
+ + setName(e.target.value)} + className="w-full bg-gray-700 text-white rounded-lg p-3 border border-gray-600 focus:border-blue-500 focus:outline-none" + placeholder="Item name..." + autoFocus + /> +
+
+ + +
+
+
+ ); +} diff --git a/frontend/src/hooks/useChecklistMutations.ts b/frontend/src/hooks/useChecklistMutations.ts new file mode 100644 index 0000000..8793e8d --- /dev/null +++ b/frontend/src/hooks/useChecklistMutations.ts @@ -0,0 +1,167 @@ +import { useApi } from './useApi'; +import { useLoader } from '../context/loaders/useLoader'; +import { useToast } from '../context/toasts/useToast'; +import { CheckItem } from '../types/kanban'; + +export function useChecklistMutations(cardId: number, onUpdate: () => void) { + const { createChecklist, deleteChecklist, createCheckItem, updateCheckItem, deleteCheckItem } = + useApi(); + const { withLoader } = useLoader(); + const { addNotification } = useToast(); + + const addChecklist = async (name: string, pos: number) => { + try { + await withLoader(() => createChecklist(cardId, { name, pos }), 'Creating checklist...'); + onUpdate(); + addNotification({ + type: 'success', + title: 'Checklist Created', + message: 'Checklist has been created successfully.', + duration: 3000, + }); + return true; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to create checklist'; + addNotification({ + type: 'error', + title: 'Error', + message: errorMessage, + duration: 5000, + }); + return false; + } + }; + + const removeChecklist = async (checklistId: number) => { + try { + await withLoader(() => deleteChecklist(checklistId), 'Deleting checklist...'); + onUpdate(); + addNotification({ + type: 'success', + title: 'Checklist Deleted', + message: 'Checklist has been deleted successfully.', + duration: 3000, + }); + return true; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to delete checklist'; + addNotification({ + type: 'error', + title: 'Error', + message: errorMessage, + duration: 5000, + }); + return false; + } + }; + + const addCheckItem = async ( + checklistId: number, + name: string, + pos: number, + state: 'incomplete' | 'complete' = 'incomplete' + ) => { + try { + await withLoader(() => createCheckItem(checklistId, { name, pos, state }), 'Adding item...'); + onUpdate(); + addNotification({ + type: 'success', + title: 'Item Added', + message: 'Check item has been added successfully.', + duration: 3000, + }); + return true; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to add item'; + addNotification({ + type: 'error', + title: 'Error', + message: errorMessage, + duration: 5000, + }); + return false; + } + }; + + const toggleCheckItem = async (item: CheckItem, currentState: 'incomplete' | 'complete') => { + console.log('item', item); + try { + const newState = currentState === 'incomplete' ? 'complete' : 'incomplete'; + await withLoader( + () => updateCheckItem(item.id, { name: item.name, pos: 0, state: newState }), + 'Updating item...' + ); + onUpdate(); + return true; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to update item'; + addNotification({ + type: 'error', + title: 'Error', + message: errorMessage, + duration: 5000, + }); + return false; + } + }; + + const editCheckItem = async ( + itemId: number, + name: string, + pos: number, + state: 'incomplete' | 'complete' + ) => { + try { + await withLoader(() => updateCheckItem(itemId, { name, pos, state }), 'Updating item...'); + onUpdate(); + addNotification({ + type: 'success', + title: 'Item Updated', + message: 'Check item has been updated successfully.', + duration: 3000, + }); + return true; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to update item'; + addNotification({ + type: 'error', + title: 'Error', + message: errorMessage, + duration: 5000, + }); + return false; + } + }; + + const removeCheckItem = async (itemId: number) => { + try { + await withLoader(() => deleteCheckItem(itemId), 'Deleting item...'); + onUpdate(); + addNotification({ + type: 'success', + title: 'Item Deleted', + message: 'Check item has been deleted successfully.', + duration: 3000, + }); + return true; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to delete item'; + addNotification({ + type: 'error', + title: 'Error', + message: errorMessage, + duration: 5000, + }); + return false; + } + }; + + return { + addChecklist, + removeChecklist, + addCheckItem, + toggleCheckItem, + editCheckItem, + removeCheckItem, + }; +} diff --git a/frontend/src/pages/CardDetail.tsx b/frontend/src/pages/CardDetail.tsx index a66804c..5a8cc08 100644 --- a/frontend/src/pages/CardDetail.tsx +++ b/frontend/src/pages/CardDetail.tsx @@ -1,12 +1,13 @@ import { useParams, Link, useNavigate } from 'react-router-dom'; import { useCard } from '../hooks/useCard'; import { useCardDetailMutations } from '../hooks/useCardDetailMutations'; +import { useChecklistMutations } from '../hooks/useChecklistMutations'; import { useModal } from '../context/modals/useModal'; import { CardSidebar } from '../components/CardSidebar'; import { CardComments } from '../components/CardComments'; +import { CardChecklists } from '../components/CardChecklists'; import { EditCardModal } from '../components/EditCardModal'; import { DeleteCardModal } from '../components/DeleteCardModal'; -import CheckSquareIcon from '../components/icons/CheckSquareIcon'; import TagIcon from '../components/icons/TagIcon'; import Trash2Icon from '../components/icons/Trash2Icon'; import ArrowLeftIcon from '../components/icons/ArrowLeftIcon'; @@ -25,6 +26,7 @@ export function CardDetail() { } = useCardDetailMutations(parseInt(cardId || '0'), card, fetchCard); const { openModal } = useModal(); + const checklistMutations = useChecklistMutations(parseInt(cardId || '0'), fetchCard); const handleEditCard = () => { if (!card) return; @@ -153,47 +155,16 @@ export function CardDetail() { )} {/* Checklists Section */} - {card.checklists && card.checklists.length > 0 && ( -
-

- - - - Checklists -

-
- {card.checklists.map((checklist: any) => ( -
-

{checklist.name}

-
- {checklist.items && checklist.items.length > 0 ? ( - checklist.items.map((item: any) => ( -
- - - {item.name} - -
- )) - ) : ( -

No items yet

- )} -
-
- ))} -
-
- )} +