Color on right - Better Handling
This commit is contained in:
1 parent
83a79dd346
commit
7f5384d0bc
6 files changed
+884
-746
No files matched your search
+369
-413
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
+28
-3
@@ -16,7 +16,7 @@
|
|||||||
<div class="context-menu-item danger" id="context-delete">🗑 Delete</div>
|
<div class="context-menu-item danger" id="context-delete">🗑 Delete</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal for creating/editing tasks -->
|
<!-- Modal -->
|
||||||
<div class="modal" id="modal">
|
<div class="modal" id="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row form-row-2">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="task-size">Size *</label>
|
<label for="task-size">Size *</label>
|
||||||
<select id="task-size" required>
|
<select id="task-size" required>
|
||||||
@@ -52,6 +52,30 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="task-due-date">Due Date</label>
|
||||||
|
<input type="date" id="task-due-date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row form-row-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Color</label>
|
||||||
|
<div class="color-picker-row">
|
||||||
|
<input type="color" id="task-color" value="#6366f1" class="color-input">
|
||||||
|
<div class="color-presets" id="color-presets">
|
||||||
|
<span class="color-dot" data-color="#6366f1" style="background:#6366f1"></span>
|
||||||
|
<span class="color-dot" data-color="#059669" style="background:#059669"></span>
|
||||||
|
<span class="color-dot" data-color="#0284c7" style="background:#0284c7"></span>
|
||||||
|
<span class="color-dot" data-color="#e11d48" style="background:#e11d48"></span>
|
||||||
|
<span class="color-dot" data-color="#d97706" style="background:#d97706"></span>
|
||||||
|
<span class="color-dot" data-color="#7c3aed" style="background:#7c3aed"></span>
|
||||||
|
<span class="color-dot" data-color="#0891b2" style="background:#0891b2"></span>
|
||||||
|
<span class="color-dot" data-color="#c026d3" style="background:#c026d3"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="task-reward">Reward</label>
|
<label for="task-reward">Reward</label>
|
||||||
<input type="text" id="task-reward" placeholder="Optional">
|
<input type="text" id="task-reward" placeholder="Optional">
|
||||||
@@ -65,6 +89,8 @@
|
|||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="greeting-banner" id="greeting-banner"></div>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1>Learning Progress Tracker</h1>
|
<h1>Learning Progress Tracker</h1>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
@@ -79,7 +105,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Overall Progress - Active view only -->
|
|
||||||
<div class="overall-progress" id="overall-progress">
|
<div class="overall-progress" id="overall-progress">
|
||||||
<div class="overall-header">
|
<div class="overall-header">
|
||||||
<span>Overall Progress</span>
|
<span>Overall Progress</span>
|
||||||
|
|||||||
+423
-239
File diff suppressed because it is too large.
Load diff
@@ -1,28 +1,33 @@
|
|||||||
# Learning Progress Tracker
|
# Learning Progress Tracker
|
||||||
|
|
||||||
A web-based goal and learning progress tracker with subtask management, drag-and-drop reordering, and persistent server-side storage.
|
A web-based goal and learning progress tracker with subtask management, due dates, drag-and-drop reordering, task archiving, and persistent server-side storage.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Goal Management** — Create, edit, and delete learning goals with titles, descriptions, sizes, and optional rewards.
|
- **Goal Management** — Create, edit, and delete learning goals with titles, descriptions, sizes, due dates, and optional rewards.
|
||||||
- **Subtasks** — Break goals into smaller subtasks. Drag any task onto another to nest it as a subtask, or promote subtasks back to top-level goals.
|
- **Due Dates** — Set deadlines on tasks and subtasks. Color-coded badges show time remaining: green (plenty of time), amber (7 days or less), red (overdue/today). Completed tasks show a strikethrough date.
|
||||||
- **Progress Tracking** — Automatic progress calculation based on subtask completion, weighted by task size (Small 10%, Medium 25%, Large 50%).
|
- **Subtasks** — Break goals into smaller subtasks. Drag any task onto another to nest it, or promote subtasks back to top-level goals via right-click.
|
||||||
- **Drag & Drop** — Reorder tasks and subtasks via drag-and-drop. Hold over a task to reveal its drop zone for nesting.
|
- **Progress Tracking** — Automatic weighted progress calculation (Small 10%, Medium 25%, Large 50%).
|
||||||
- **Context Menus** — Right-click any task or subtask to edit, delete, or promote (subtasks only).
|
- **Archive** — Completed tasks show an "Archive" button. Switch to the Archive view to restore, edit, or permanently delete archived tasks.
|
||||||
- **Rewards** — Set optional rewards on goals that are revealed only upon completion.
|
- **Drag & Drop** — Reorder tasks and subtasks. Hold over a task to reveal its drop zone for nesting.
|
||||||
- **Persistent Storage** — All data is stored in a SQLite database on the server.
|
- **Context Menus** — Right-click for edit, delete, promote, archive, or restore actions.
|
||||||
- **Dark Theme** — Clean, modern dark UI.
|
- **Double-Click to Edit** — Double-click any task or subtask card to open the editor.
|
||||||
|
- **Expandable Descriptions** — Toggle "Show full description" per task to expand beyond the default 2-line clamp.
|
||||||
|
- **Rewards** — Hidden rewards revealed only upon completion.
|
||||||
|
- **Custom Background** — Place a `background.jpg` or `background.png` in `public/img/` to set a custom background image. The UI uses frosted glass (glassmorphism) over the image.
|
||||||
|
- **Persistent Storage** — SQLite database with automatic schema migration.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
├── public/
|
├── public/
|
||||||
│ ├── index.html # Main HTML page
|
│ ├── img/ # Place background.jpg or background.png here
|
||||||
│ ├── styles.css # Stylesheet (dark theme)
|
│ ├── index.html
|
||||||
│ └── app.js # Client-side application logic
|
│ ├── styles.css
|
||||||
|
│ └── app.js
|
||||||
├── server/
|
├── server/
|
||||||
│ └── server.js # Express server with SQLite backend
|
│ └── server.js
|
||||||
├── data/ # SQLite database (auto-created)
|
├── data/ # SQLite database (auto-created)
|
||||||
├── package.json
|
├── package.json
|
||||||
├── dockerfile
|
├── dockerfile
|
||||||
└── README.md
|
└── README.md
|
||||||
@@ -33,22 +38,16 @@ A web-based goal and learning progress tracker with subtask management, drag-and
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/) v18 or later
|
- [Node.js](https://nodejs.org/) v18 or later
|
||||||
- npm (comes with Node.js)
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone or download the project
|
|
||||||
cd learning-progress-tracker
|
cd learning-progress-tracker
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Start the server
|
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
The app will be available at **http://localhost:3000**.
|
Available at **http://localhost:3000**.
|
||||||
|
|
||||||
For development with auto-reload:
|
For development with auto-reload:
|
||||||
|
|
||||||
@@ -56,13 +55,14 @@ For development with auto-reload:
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Custom Background
|
||||||
|
|
||||||
|
Place your image at `public/img/background.jpg` (or `.png`). The CSS references `img/background.jpg` — rename or edit the CSS path as needed. Without an image, the app falls back to a soft gradient.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build the image
|
|
||||||
docker build -t learning-tracker .
|
docker build -t learning-tracker .
|
||||||
|
|
||||||
# Run the container (persists data via volume)
|
|
||||||
docker run -d -p 3000:3000 -v tracker-data:/app/data learning-tracker
|
docker run -d -p 3000:3000 -v tracker-data:/app/data learning-tracker
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -70,22 +70,25 @@ docker run -d -p 3000:3000 -v tracker-data:/app/data learning-tracker
|
|||||||
|
|
||||||
| Method | Endpoint | Description |
|
| Method | Endpoint | Description |
|
||||||
|--------|----------|-------------|
|
|--------|----------|-------------|
|
||||||
| `GET` | `/api/tasks` | Fetch all tasks |
|
| `GET` | `/api/tasks` | Fetch active tasks (`?archived=1` for archived) |
|
||||||
| `POST` | `/api/tasks` | Create a new task |
|
| `POST` | `/api/tasks` | Create a new task |
|
||||||
| `PUT` | `/api/tasks/:id` | Update a task (including subtasks) |
|
| `PUT` | `/api/tasks/:id` | Update a task |
|
||||||
| `DELETE` | `/api/tasks/:id` | Delete a task |
|
| `PUT` | `/api/tasks/:id/archive` | Archive a task |
|
||||||
| `POST` | `/api/tasks/reorder` | Reorder tasks (accepts array of task IDs) |
|
| `PUT` | `/api/tasks/:id/unarchive` | Restore an archived task |
|
||||||
|
| `DELETE` | `/api/tasks/:id` | Permanently delete a task |
|
||||||
|
| `POST` | `/api/tasks/reorder` | Reorder tasks (array of IDs) |
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- Click **+ New Goal** to create a task.
|
- **+ New Goal** to create a task with optional due date.
|
||||||
- **Right-click** a task or subtask for edit/delete/promote options.
|
- **Right-click** for context menu actions.
|
||||||
- **Drag** tasks to reorder them, or hold over another task to nest it as a subtask.
|
- **Double-click** a card to edit it.
|
||||||
- Mark tasks complete to reveal rewards and track overall progress.
|
- **Drag** to reorder or nest tasks.
|
||||||
|
- **Archive** completed tasks, then switch views with the Active/Archive toggle.
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **Frontend:** Vanilla JavaScript, HTML, CSS
|
- **Frontend:** Vanilla JavaScript, HTML, CSS (glassmorphism, CSS variables)
|
||||||
- **Backend:** Node.js, Express
|
- **Backend:** Node.js, Express
|
||||||
- **Database:** SQLite3
|
- **Database:** SQLite3
|
||||||
- **Containerization:** Docker
|
- **Containerization:** Docker
|
||||||
|
|||||||
+28
-58
@@ -7,17 +7,14 @@ const fs = require('fs');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
// Middleware
|
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(express.static(path.join(__dirname, '../public')));
|
app.use(express.static(path.join(__dirname, '../public')));
|
||||||
|
|
||||||
// Ensure data directory exists
|
|
||||||
const dataDir = path.join(__dirname, '../data');
|
const dataDir = path.join(__dirname, '../data');
|
||||||
if (!fs.existsSync(dataDir)) {
|
if (!fs.existsSync(dataDir)) {
|
||||||
fs.mkdirSync(dataDir, { recursive: true });
|
fs.mkdirSync(dataDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize SQLite database
|
|
||||||
const dbPath = process.env.DATABASE_PATH || path.join(dataDir, 'tasks.db');
|
const dbPath = process.env.DATABASE_PATH || path.join(dataDir, 'tasks.db');
|
||||||
const db = new sqlite3.Database(dbPath, (err) => {
|
const db = new sqlite3.Database(dbPath, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -39,6 +36,8 @@ function initDatabase() {
|
|||||||
completed INTEGER DEFAULT 0,
|
completed INTEGER DEFAULT 0,
|
||||||
archived INTEGER DEFAULT 0,
|
archived INTEGER DEFAULT 0,
|
||||||
expand_description INTEGER DEFAULT 0,
|
expand_description INTEGER DEFAULT 0,
|
||||||
|
due_date TEXT,
|
||||||
|
color TEXT DEFAULT '#6366f1',
|
||||||
position INTEGER,
|
position INTEGER,
|
||||||
subtasks TEXT,
|
subtasks TEXT,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
@@ -46,15 +45,13 @@ function initDatabase() {
|
|||||||
)
|
)
|
||||||
`, (err) => {
|
`, (err) => {
|
||||||
if (err) return;
|
if (err) return;
|
||||||
// Migrate: add columns if missing on existing databases
|
|
||||||
db.run(`ALTER TABLE tasks ADD COLUMN archived INTEGER DEFAULT 0`, () => {});
|
db.run(`ALTER TABLE tasks ADD COLUMN archived INTEGER DEFAULT 0`, () => {});
|
||||||
db.run(`ALTER TABLE tasks ADD COLUMN expand_description INTEGER DEFAULT 0`, () => {});
|
db.run(`ALTER TABLE tasks ADD COLUMN expand_description INTEGER DEFAULT 0`, () => {});
|
||||||
|
db.run(`ALTER TABLE tasks ADD COLUMN due_date TEXT`, () => {});
|
||||||
|
db.run(`ALTER TABLE tasks ADD COLUMN color TEXT DEFAULT '#6366f1'`, () => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Routes
|
|
||||||
|
|
||||||
// Get tasks — ?archived=1 returns archived, default returns active
|
|
||||||
app.get('/api/tasks', (req, res) => {
|
app.get('/api/tasks', (req, res) => {
|
||||||
const archived = req.query.archived === '1' ? 1 : 0;
|
const archived = req.query.archived === '1' ? 1 : 0;
|
||||||
db.all('SELECT * FROM tasks WHERE archived = ? ORDER BY position ASC, created_at ASC', [archived], (err, rows) => {
|
db.all('SELECT * FROM tasks WHERE archived = ? ORDER BY position ASC, created_at ASC', [archived], (err, rows) => {
|
||||||
@@ -62,7 +59,6 @@ app.get('/api/tasks', (req, res) => {
|
|||||||
console.error('Error fetching tasks:', err);
|
console.error('Error fetching tasks:', err);
|
||||||
return res.status(500).json({ error: 'Failed to fetch tasks' });
|
return res.status(500).json({ error: 'Failed to fetch tasks' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks = rows.map(row => ({
|
const tasks = rows.map(row => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
title: row.title,
|
title: row.title,
|
||||||
@@ -72,53 +68,45 @@ app.get('/api/tasks', (req, res) => {
|
|||||||
completed: row.completed === 1,
|
completed: row.completed === 1,
|
||||||
archived: row.archived === 1,
|
archived: row.archived === 1,
|
||||||
expandDescription: row.expand_description === 1,
|
expandDescription: row.expand_description === 1,
|
||||||
|
dueDate: row.due_date || null,
|
||||||
|
color: row.color || '#6366f1',
|
||||||
subtasks: row.subtasks ? JSON.parse(row.subtasks) : []
|
subtasks: row.subtasks ? JSON.parse(row.subtasks) : []
|
||||||
}));
|
}));
|
||||||
|
|
||||||
res.json(tasks);
|
res.json(tasks);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a new task
|
|
||||||
app.post('/api/tasks', (req, res) => {
|
app.post('/api/tasks', (req, res) => {
|
||||||
const { title, description, size, reward, expandDescription } = req.body;
|
const { title, description, size, reward, expandDescription, dueDate, color } = req.body;
|
||||||
const id = Date.now().toString();
|
const id = Date.now().toString();
|
||||||
|
|
||||||
db.run(
|
db.run(
|
||||||
`INSERT INTO tasks (id, title, description, size, reward, expand_description, archived, position)
|
`INSERT INTO tasks (id, title, description, size, reward, expand_description, due_date, color, archived, position)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, 0, (SELECT COALESCE(MAX(position), 0) + 1 FROM tasks WHERE archived = 0))`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, (SELECT COALESCE(MAX(position), 0) + 1 FROM tasks WHERE archived = 0))`,
|
||||||
[id, title, description || '', size, reward || '', expandDescription ? 1 : 0],
|
[id, title, description || '', size, reward || '', expandDescription ? 1 : 0, dueDate || null, color || '#6366f1'],
|
||||||
function(err) {
|
function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error creating task:', err);
|
console.error('Error creating task:', err);
|
||||||
return res.status(500).json({ error: 'Failed to create task' });
|
return res.status(500).json({ error: 'Failed to create task' });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
id,
|
id, title, description: description || '', size, reward: reward || '',
|
||||||
title,
|
completed: false, archived: false, expandDescription: !!expandDescription,
|
||||||
description: description || '',
|
dueDate: dueDate || null, color: color || '#6366f1', subtasks: []
|
||||||
size,
|
|
||||||
reward: reward || '',
|
|
||||||
completed: false,
|
|
||||||
archived: false,
|
|
||||||
expandDescription: !!expandDescription,
|
|
||||||
subtasks: []
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update a task
|
|
||||||
app.put('/api/tasks/:id', (req, res) => {
|
app.put('/api/tasks/:id', (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { title, description, size, reward, completed, expandDescription, subtasks } = req.body;
|
const { title, description, size, reward, completed, expandDescription, dueDate, color, subtasks } = req.body;
|
||||||
|
|
||||||
db.run(
|
db.run(
|
||||||
`UPDATE tasks
|
`UPDATE tasks
|
||||||
SET title = ?, description = ?, size = ?, reward = ?, completed = ?, expand_description = ?, subtasks = ?, updated_at = CURRENT_TIMESTAMP
|
SET title = ?, description = ?, size = ?, reward = ?, completed = ?, expand_description = ?, due_date = ?, color = ?, subtasks = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?`,
|
WHERE id = ?`,
|
||||||
[title, description || '', size, reward || '', completed ? 1 : 0, expandDescription ? 1 : 0, JSON.stringify(subtasks || []), id],
|
[title, description || '', size, reward || '', completed ? 1 : 0, expandDescription ? 1 : 0, dueDate || null, color || '#6366f1', JSON.stringify(subtasks || []), id],
|
||||||
function(err) {
|
function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error updating task:', err);
|
console.error('Error updating task:', err);
|
||||||
@@ -130,27 +118,20 @@ app.put('/api/tasks/:id', (req, res) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Archive a task
|
|
||||||
app.put('/api/tasks/:id/archive', (req, res) => {
|
app.put('/api/tasks/:id/archive', (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
db.run(
|
db.run(`UPDATE tasks SET archived = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, [id], function(err) {
|
||||||
`UPDATE tasks SET archived = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
|
if (err) return res.status(500).json({ error: 'Failed to archive task' });
|
||||||
[id],
|
if (this.changes === 0) return res.status(404).json({ error: 'Task not found' });
|
||||||
function(err) {
|
res.json({ success: true });
|
||||||
if (err) return res.status(500).json({ error: 'Failed to archive task' });
|
});
|
||||||
if (this.changes === 0) return res.status(404).json({ error: 'Task not found' });
|
|
||||||
res.json({ success: true });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unarchive (restore) a task
|
|
||||||
app.put('/api/tasks/:id/unarchive', (req, res) => {
|
app.put('/api/tasks/:id/unarchive', (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
db.run(
|
db.run(
|
||||||
`UPDATE tasks SET archived = 0, position = (SELECT COALESCE(MAX(position), 0) + 1 FROM tasks WHERE archived = 0), updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
|
`UPDATE tasks SET archived = 0, position = (SELECT COALESCE(MAX(position), 0) + 1 FROM tasks WHERE archived = 0), updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
|
||||||
[id],
|
[id], function(err) {
|
||||||
function(err) {
|
|
||||||
if (err) return res.status(500).json({ error: 'Failed to unarchive task' });
|
if (err) return res.status(500).json({ error: 'Failed to unarchive task' });
|
||||||
if (this.changes === 0) return res.status(404).json({ error: 'Task not found' });
|
if (this.changes === 0) return res.status(404).json({ error: 'Task not found' });
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
@@ -158,7 +139,6 @@ app.put('/api/tasks/:id/unarchive', (req, res) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete a task
|
|
||||||
app.delete('/api/tasks/:id', (req, res) => {
|
app.delete('/api/tasks/:id', (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
db.run('DELETE FROM tasks WHERE id = ?', [id], function(err) {
|
db.run('DELETE FROM tasks WHERE id = ?', [id], function(err) {
|
||||||
@@ -168,29 +148,19 @@ app.delete('/api/tasks/:id', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reorder tasks
|
|
||||||
app.post('/api/tasks/reorder', (req, res) => {
|
app.post('/api/tasks/reorder', (req, res) => {
|
||||||
const taskIds = req.body;
|
const taskIds = req.body;
|
||||||
if (!Array.isArray(taskIds)) return res.status(400).json({ error: 'Invalid task order data' });
|
if (!Array.isArray(taskIds)) return res.status(400).json({ error: 'Invalid task order data' });
|
||||||
|
const promises = taskIds.map((id, index) =>
|
||||||
const promises = taskIds.map((id, index) => {
|
new Promise((resolve, reject) => {
|
||||||
return new Promise((resolve, reject) => {
|
db.run('UPDATE tasks SET position = ? WHERE id = ?', [index, id], (err) => err ? reject(err) : resolve());
|
||||||
db.run('UPDATE tasks SET position = ? WHERE id = ?', [index, id], (err) => {
|
})
|
||||||
if (err) reject(err);
|
);
|
||||||
else resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.all(promises)
|
Promise.all(promises)
|
||||||
.then(() => res.json({ success: true }))
|
.then(() => res.json({ success: true }))
|
||||||
.catch(err => {
|
.catch(err => { console.error('Error reordering tasks:', err); res.status(500).json({ error: 'Failed to reorder tasks' }); });
|
||||||
console.error('Error reordering tasks:', err);
|
|
||||||
res.status(500).json({ error: 'Failed to reorder tasks' });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve the main HTML file
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, '../public/index.html'));
|
res.sendFile(path.join(__dirname, '../public/index.html'));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user