feat: Add NoteForm, ProjectForm, and ProjectTaskForm components
- Implemented NoteForm for adding notes to projects. - Created ProjectForm for managing project details with contract selection. - Developed ProjectTaskForm for adding tasks to projects, supporting both templates and custom tasks. feat: Add ProjectTasksSection component - Introduced ProjectTasksSection to display and manage tasks for a specific project. - Includes functionality for adding, updating, and deleting tasks. feat: Create TaskTemplateForm for managing task templates - Added TaskTemplateForm for creating new task templates with required wait days. feat: Implement UI components - Created reusable UI components: Badge, Button, Card, Input, Loading, Navigation. - Enhanced user experience with consistent styling and functionality. feat: Set up database and queries - Initialized SQLite database with tables for contracts, projects, tasks, project tasks, and notes. - Implemented queries for managing contracts, projects, tasks, and notes. chore: Add error handling and loading states - Improved error handling in forms and data fetching. - Added loading states for better user feedback during data operations.
This commit is contained in:
4
src/lib/db.js
Normal file
4
src/lib/db.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// src/lib/db.ts
|
||||
import Database from "better-sqlite3";
|
||||
const db = new Database("data/database.sqlite");
|
||||
export default db;
|
||||
85
src/lib/init-db.js
Normal file
85
src/lib/init-db.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import db from "./db";
|
||||
|
||||
export default function initializeDatabase() {
|
||||
db.exec(`
|
||||
-- Table: contracts
|
||||
CREATE TABLE IF NOT EXISTS contracts (
|
||||
contract_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
contract_number TEXT NOT NULL,
|
||||
contract_name TEXT,
|
||||
customer_contract_number TEXT,
|
||||
customer TEXT,
|
||||
investor TEXT,
|
||||
date_signed TEXT,
|
||||
finish_date TEXT
|
||||
);
|
||||
|
||||
-- Table: projects
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
project_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
contract_id INTEGER,
|
||||
project_name TEXT NOT NULL,
|
||||
project_number TEXT NOT NULL,
|
||||
address TEXT,
|
||||
plot TEXT,
|
||||
district TEXT,
|
||||
unit TEXT,
|
||||
city TEXT,
|
||||
investment_number TEXT,
|
||||
finish_date TEXT,
|
||||
wp TEXT,
|
||||
contact TEXT,
|
||||
notes TEXT,
|
||||
FOREIGN KEY (contract_id) REFERENCES contracts(contract_id)
|
||||
);
|
||||
|
||||
-- Table: tasks
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
task_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
max_wait_days INTEGER DEFAULT 0,
|
||||
is_standard INTEGER NOT NULL DEFAULT 0
|
||||
); -- Table: project_tasks
|
||||
CREATE TABLE IF NOT EXISTS project_tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
project_id INTEGER NOT NULL,
|
||||
task_template_id INTEGER,
|
||||
custom_task_name TEXT,
|
||||
custom_max_wait_days INTEGER DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
date_added TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
priority TEXT DEFAULT 'normal',
|
||||
FOREIGN KEY (project_id) REFERENCES projects(project_id),
|
||||
FOREIGN KEY (task_template_id) REFERENCES tasks(task_id)
|
||||
);
|
||||
|
||||
-- Table: notes
|
||||
CREATE TABLE IF NOT EXISTS notes (
|
||||
note_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
project_id INTEGER,
|
||||
task_id INTEGER,
|
||||
note TEXT NOT NULL,
|
||||
note_date TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (project_id) REFERENCES projects(project_id),
|
||||
FOREIGN KEY (task_id) REFERENCES project_tasks(id)
|
||||
);
|
||||
|
||||
`);
|
||||
|
||||
// Migration: Add custom task columns if they don't exist
|
||||
try {
|
||||
db.exec(`
|
||||
ALTER TABLE project_tasks ADD COLUMN custom_task_name TEXT;
|
||||
`);
|
||||
} catch (e) {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
|
||||
try {
|
||||
db.exec(`
|
||||
ALTER TABLE project_tasks ADD COLUMN custom_max_wait_days INTEGER DEFAULT 0;
|
||||
`);
|
||||
} catch (e) {
|
||||
// Column already exists, ignore error
|
||||
}
|
||||
}
|
||||
0
src/lib/queries/contracts.js
Normal file
0
src/lib/queries/contracts.js
Normal file
14
src/lib/queries/notes.js
Normal file
14
src/lib/queries/notes.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import db from "../db.js";
|
||||
|
||||
export function getNotesByProjectId(project_id) {
|
||||
return db
|
||||
.prepare(`SELECT * FROM notes WHERE project_id = ? ORDER BY note_date DESC`)
|
||||
.all(project_id);
|
||||
}
|
||||
|
||||
export function addNoteToProject(project_id, note) {
|
||||
db.prepare(`INSERT INTO notes (project_id, note) VALUES (?, ?)`).run(
|
||||
project_id,
|
||||
note
|
||||
);
|
||||
}
|
||||
105
src/lib/queries/projects.js
Normal file
105
src/lib/queries/projects.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import db from "../db.js";
|
||||
|
||||
export function getAllProjects() {
|
||||
return db.prepare("SELECT * FROM projects ORDER BY finish_date DESC").all();
|
||||
}
|
||||
|
||||
export function getProjectById(id) {
|
||||
return db.prepare("SELECT * FROM projects WHERE project_id = ?").get(id);
|
||||
}
|
||||
|
||||
export function createProject(data) {
|
||||
// 1. Get the max project_number under this contract
|
||||
const existing = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT MAX(project_number) as max_number
|
||||
FROM projects
|
||||
WHERE contract_id = ?
|
||||
`
|
||||
)
|
||||
.get(data.contract_id);
|
||||
|
||||
const nextNumber = (existing.max_number || 0) + 1;
|
||||
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO projects (
|
||||
contract_id, project_name, project_number, address, plot, district, unit, city, investment_number, finish_date,
|
||||
wp, contact, notes
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
stmt.run(
|
||||
data.contract_id,
|
||||
data.project_name,
|
||||
parseInt(nextNumber),
|
||||
data.address,
|
||||
data.plot,
|
||||
data.district,
|
||||
data.unit,
|
||||
data.city,
|
||||
data.investment_number,
|
||||
data.finish_date,
|
||||
data.wp,
|
||||
data.contact,
|
||||
data.notes
|
||||
);
|
||||
}
|
||||
|
||||
export function updateProject(id, data) {
|
||||
const stmt = db.prepare(`
|
||||
UPDATE projects SET
|
||||
contract_id = ?, project_name = ?, project_number = ?, address = ?, plot = ?, district = ?, unit = ?, city = ?,
|
||||
investment_number = ?, finish_date = ?, wp = ?, contact = ?, notes = ?
|
||||
WHERE project_id = ?
|
||||
`);
|
||||
stmt.run(
|
||||
data.contract_id,
|
||||
data.project_name,
|
||||
data.project_number,
|
||||
data.address,
|
||||
data.plot,
|
||||
data.district,
|
||||
data.unit,
|
||||
data.city,
|
||||
data.investment_number,
|
||||
data.finish_date,
|
||||
data.wp,
|
||||
data.contact,
|
||||
data.notes,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteProject(id) {
|
||||
db.prepare("DELETE FROM projects WHERE project_id = ?").run(id);
|
||||
}
|
||||
|
||||
export function getProjectWithContract(id) {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
p.*,
|
||||
c.contract_number,
|
||||
c.contract_name,
|
||||
c.customer,
|
||||
c.investor
|
||||
FROM projects p
|
||||
LEFT JOIN contracts c ON p.contract_id = c.contract_id
|
||||
WHERE p.project_id = ?
|
||||
`
|
||||
)
|
||||
.get(id);
|
||||
}
|
||||
|
||||
export function getNotesForProject(projectId) {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT * FROM notes
|
||||
WHERE project_id = ?
|
||||
ORDER BY note_date DESC
|
||||
`
|
||||
)
|
||||
.all(projectId);
|
||||
}
|
||||
76
src/lib/queries/tasks.js
Normal file
76
src/lib/queries/tasks.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import db from "../db.js";
|
||||
|
||||
// Get all task templates (for dropdown selection)
|
||||
export function getAllTaskTemplates() {
|
||||
return db
|
||||
.prepare("SELECT * FROM tasks WHERE is_standard = 1 ORDER BY name ASC")
|
||||
.all();
|
||||
}
|
||||
|
||||
// Get project tasks for a specific project
|
||||
export function getProjectTasks(projectId) {
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
pt.*,
|
||||
COALESCE(pt.custom_task_name, t.name) as task_name,
|
||||
COALESCE(pt.custom_max_wait_days, t.max_wait_days) as max_wait_days,
|
||||
CASE
|
||||
WHEN pt.task_template_id IS NOT NULL THEN 'template'
|
||||
ELSE 'custom'
|
||||
END as task_type
|
||||
FROM project_tasks pt
|
||||
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
|
||||
WHERE pt.project_id = ?
|
||||
ORDER BY pt.date_added DESC
|
||||
`
|
||||
)
|
||||
.all(projectId);
|
||||
}
|
||||
|
||||
// Create a new project task
|
||||
export function createProjectTask(data) {
|
||||
if (data.task_template_id) {
|
||||
// Creating from template
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO project_tasks (project_id, task_template_id, status, priority)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`);
|
||||
return stmt.run(
|
||||
data.project_id,
|
||||
data.task_template_id,
|
||||
data.status || "pending",
|
||||
data.priority || "normal"
|
||||
);
|
||||
} else {
|
||||
// Creating custom task
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO project_tasks (project_id, custom_task_name, custom_max_wait_days, status, priority)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`);
|
||||
return stmt.run(
|
||||
data.project_id,
|
||||
data.custom_task_name,
|
||||
data.custom_max_wait_days || 0,
|
||||
data.status || "pending",
|
||||
data.priority || "normal"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update project task status
|
||||
export function updateProjectTaskStatus(taskId, status) {
|
||||
const stmt = db.prepare(`
|
||||
UPDATE project_tasks
|
||||
SET status = ?
|
||||
WHERE id = ?
|
||||
`);
|
||||
return stmt.run(status, taskId);
|
||||
}
|
||||
|
||||
// Delete a project task
|
||||
export function deleteProjectTask(taskId) {
|
||||
const stmt = db.prepare("DELETE FROM project_tasks WHERE id = ?");
|
||||
return stmt.run(taskId);
|
||||
}
|
||||
Reference in New Issue
Block a user