feat: Add dueDate (date+time) to notes and update reminders to datetime

- Add dueDate timestamp field to notes schema
- Update note validators to accept dueDate
- Update note service to handle dueDate in CRUD operations
- Fix company and project controllers to pass dueDate
- Fix route validations to include dueDate field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
richardtekula
2025-12-15 07:03:29 +01:00
parent 8770a98db8
commit f828af562d
7 changed files with 30 additions and 12 deletions

View File

@@ -208,11 +208,12 @@ export const addCompanyNote = async (req, res, next) => {
try { try {
const userId = req.userId; const userId = req.userId;
const { companyId } = req.params; const { companyId } = req.params;
const { content } = req.body; const { content, dueDate } = req.body;
const note = await noteService.createNote(userId, { const note = await noteService.createNote(userId, {
content, content,
companyId, companyId,
dueDate,
}); });
res.status(201).json({ res.status(201).json({
@@ -232,10 +233,11 @@ export const addCompanyNote = async (req, res, next) => {
export const updateCompanyNote = async (req, res, next) => { export const updateCompanyNote = async (req, res, next) => {
try { try {
const { noteId } = req.params; const { noteId } = req.params;
const { content } = req.body; const { content, dueDate } = req.body;
const note = await noteService.updateNote(noteId, { const note = await noteService.updateNote(noteId, {
content, content,
dueDate,
}); });
res.status(200).json({ res.status(200).json({

View File

@@ -165,12 +165,12 @@ export const addProjectNote = async (req, res, next) => {
try { try {
const userId = req.userId; const userId = req.userId;
const { projectId } = req.params; const { projectId } = req.params;
const { content, reminderAt } = req.body; const { content, dueDate } = req.body;
const note = await noteService.createNote(userId, { const note = await noteService.createNote(userId, {
content, content,
projectId, projectId,
reminderDate: reminderAt, // Map reminderAt to reminderDate dueDate,
}); });
res.status(201).json({ res.status(201).json({
@@ -190,11 +190,11 @@ export const addProjectNote = async (req, res, next) => {
export const updateProjectNote = async (req, res, next) => { export const updateProjectNote = async (req, res, next) => {
try { try {
const { noteId } = req.params; const { noteId } = req.params;
const { content, reminderAt } = req.body; const { content, dueDate } = req.body;
const note = await noteService.updateNote(noteId, { const note = await noteService.updateNote(noteId, {
content, content,
reminderDate: reminderAt, // Map reminderAt to reminderDate dueDate,
}); });
res.status(200).json({ res.status(200).json({

View File

@@ -210,7 +210,7 @@ export const todoUsers = pgTable('todo_users', {
todoUserUnique: unique('todo_user_unique').on(table.todoId, table.userId), todoUserUnique: unique('todo_user_unique').on(table.todoId, table.userId),
})); }));
// Notes table - poznámky (bez reminder funkcionalít) // Notes table - poznámky s voliteľným dátumom a časom splnenia
export const notes = pgTable('notes', { export const notes = pgTable('notes', {
id: uuid('id').primaryKey().defaultRandom(), id: uuid('id').primaryKey().defaultRandom(),
title: text('title'), title: text('title'),
@@ -219,6 +219,7 @@ export const notes = pgTable('notes', {
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'cascade' }), // alebo projektu projectId: uuid('project_id').references(() => projects.id, { onDelete: 'cascade' }), // alebo projektu
todoId: uuid('todo_id').references(() => todos.id, { onDelete: 'cascade' }), // alebo todo todoId: uuid('todo_id').references(() => todos.id, { onDelete: 'cascade' }), // alebo todo
contactId: uuid('contact_id').references(() => contacts.id, { onDelete: 'cascade' }), // alebo kontaktu contactId: uuid('contact_id').references(() => contacts.id, { onDelete: 'cascade' }), // alebo kontaktu
dueDate: timestamp('due_date'), // voliteľný dátum a čas splnenia (24h formát)
createdBy: uuid('created_by').references(() => users.id, { onDelete: 'set null' }), createdBy: uuid('created_by').references(() => users.id, { onDelete: 'set null' }),
createdAt: timestamp('created_at').defaultNow().notNull(), createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(), updatedAt: timestamp('updated_at').defaultNow().notNull(),

View File

@@ -83,6 +83,7 @@ router.post(
validateParams(z.object({ companyId: z.string().uuid() })), validateParams(z.object({ companyId: z.string().uuid() })),
validateBody(z.object({ validateBody(z.object({
content: z.string().min(1), content: z.string().min(1),
dueDate: z.string().optional().or(z.literal('')),
})), })),
companyController.addCompanyNote companyController.addCompanyNote
); );
@@ -96,6 +97,7 @@ router.patch(
})), })),
validateBody(z.object({ validateBody(z.object({
content: z.string().min(1).optional(), content: z.string().min(1).optional(),
dueDate: z.string().optional().or(z.literal('').or(z.null())),
})), })),
companyController.updateCompanyNote companyController.updateCompanyNote
); );

View File

@@ -66,7 +66,7 @@ router.post(
validateParams(z.object({ projectId: z.string().uuid() })), validateParams(z.object({ projectId: z.string().uuid() })),
validateBody(z.object({ validateBody(z.object({
content: z.string().min(1), content: z.string().min(1),
reminderAt: z.string().optional().or(z.literal('')), dueDate: z.string().optional().or(z.literal('')),
})), })),
projectController.addProjectNote projectController.addProjectNote
); );
@@ -80,7 +80,7 @@ router.patch(
})), })),
validateBody(z.object({ validateBody(z.object({
content: z.string().min(1).optional(), content: z.string().min(1).optional(),
reminderAt: z.string().optional().or(z.literal('').or(z.null())), dueDate: z.string().optional().or(z.literal('').or(z.null())),
})), })),
projectController.updateProjectNote projectController.updateProjectNote
); );

View File

@@ -20,6 +20,7 @@ export const getAllNotes = async (filters = {}) => {
projectId: notes.projectId, projectId: notes.projectId,
todoId: notes.todoId, todoId: notes.todoId,
contactId: notes.contactId, contactId: notes.contactId,
dueDate: notes.dueDate,
createdBy: notes.createdBy, createdBy: notes.createdBy,
createdAt: notes.createdAt, createdAt: notes.createdAt,
updatedAt: notes.updatedAt, updatedAt: notes.updatedAt,
@@ -86,7 +87,7 @@ export const getNoteById = async (noteId) => {
* Create new note * Create new note
*/ */
export const createNote = async (userId, data) => { export const createNote = async (userId, data) => {
const { title, content, companyId, projectId, todoId, contactId } = data; const { title, content, companyId, projectId, todoId, contactId, dueDate } = data;
// Verify company exists if provided // Verify company exists if provided
if (companyId) { if (companyId) {
@@ -149,6 +150,7 @@ export const createNote = async (userId, data) => {
projectId: projectId || null, projectId: projectId || null,
todoId: todoId || null, todoId: todoId || null,
contactId: contactId || null, contactId: contactId || null,
dueDate: dueDate ? new Date(dueDate) : null,
createdBy: userId, createdBy: userId,
}) })
.returning(); .returning();
@@ -162,7 +164,7 @@ export const createNote = async (userId, data) => {
export const updateNote = async (noteId, data) => { export const updateNote = async (noteId, data) => {
const note = await getNoteById(noteId); const note = await getNoteById(noteId);
const { title, content, companyId, projectId, todoId, contactId } = data; const { title, content, companyId, projectId, todoId, contactId, dueDate } = data;
// Verify company exists if being changed // Verify company exists if being changed
if (companyId !== undefined && companyId !== null && companyId !== note.companyId) { if (companyId !== undefined && companyId !== null && companyId !== note.companyId) {
@@ -216,6 +218,12 @@ export const updateNote = async (noteId, data) => {
} }
} }
// Spracovanie dueDate - konverzia na Date objekt alebo null
let parsedDueDate = note.dueDate;
if (dueDate !== undefined) {
parsedDueDate = dueDate ? new Date(dueDate) : null;
}
const [updated] = await db const [updated] = await db
.update(notes) .update(notes)
.set({ .set({
@@ -225,6 +233,7 @@ export const updateNote = async (noteId, data) => {
projectId: projectId !== undefined ? projectId : note.projectId, projectId: projectId !== undefined ? projectId : note.projectId,
todoId: todoId !== undefined ? todoId : note.todoId, todoId: todoId !== undefined ? todoId : note.todoId,
contactId: contactId !== undefined ? contactId : note.contactId, contactId: contactId !== undefined ? contactId : note.contactId,
dueDate: parsedDueDate,
updatedAt: new Date(), updatedAt: new Date(),
}) })
.where(eq(notes.id, noteId)) .where(eq(notes.id, noteId))
@@ -258,6 +267,7 @@ export const getNotesByCompanyId = async (companyId) => {
projectId: notes.projectId, projectId: notes.projectId,
todoId: notes.todoId, todoId: notes.todoId,
contactId: notes.contactId, contactId: notes.contactId,
dueDate: notes.dueDate,
createdBy: notes.createdBy, createdBy: notes.createdBy,
createdAt: notes.createdAt, createdAt: notes.createdAt,
updatedAt: notes.updatedAt, updatedAt: notes.updatedAt,
@@ -286,6 +296,7 @@ export const getNotesByProjectId = async (projectId) => {
projectId: notes.projectId, projectId: notes.projectId,
todoId: notes.todoId, todoId: notes.todoId,
contactId: notes.contactId, contactId: notes.contactId,
dueDate: notes.dueDate,
createdBy: notes.createdBy, createdBy: notes.createdBy,
createdAt: notes.createdAt, createdAt: notes.createdAt,
updatedAt: notes.updatedAt, updatedAt: notes.updatedAt,

View File

@@ -81,7 +81,7 @@ export const updateTodoSchema = z.object({
dueDate: z.string().optional().or(z.literal('').or(z.null())), dueDate: z.string().optional().or(z.literal('').or(z.null())),
}); });
// Note validators (without reminder functionality) // Note validators (s voliteľným dueDate pre dátum a čas)
export const createNoteSchema = z.object({ export const createNoteSchema = z.object({
title: z.string().max(255).optional(), title: z.string().max(255).optional(),
content: z content: z
@@ -94,6 +94,7 @@ export const createNoteSchema = z.object({
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('')), projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('')),
todoId: z.string().uuid('Neplatný formát todo ID').optional().or(z.literal('')), todoId: z.string().uuid('Neplatný formát todo ID').optional().or(z.literal('')),
contactId: z.string().uuid('Neplatný formát contact ID').optional().or(z.literal('')), contactId: z.string().uuid('Neplatný formát contact ID').optional().or(z.literal('')),
dueDate: z.string().optional().or(z.literal('')), // ISO string s dátumom a časom (24h formát)
}); });
export const updateNoteSchema = z.object({ export const updateNoteSchema = z.object({
@@ -103,6 +104,7 @@ export const updateNoteSchema = z.object({
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('').or(z.null())), projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('').or(z.null())),
todoId: z.string().uuid('Neplatný formát todo ID').optional().or(z.literal('').or(z.null())), todoId: z.string().uuid('Neplatný formát todo ID').optional().or(z.literal('').or(z.null())),
contactId: z.string().uuid('Neplatný formát contact ID').optional().or(z.literal('').or(z.null())), contactId: z.string().uuid('Neplatný formát contact ID').optional().or(z.literal('').or(z.null())),
dueDate: z.string().optional().or(z.literal('').or(z.null())), // ISO string s dátumom a časom (24h formát)
}); });
// Company reminder validators (with dueDate) // Company reminder validators (with dueDate)