feat: Multi-feature CRM update
- Add team_leader role with appropriate permissions - Add lastSeen timestamp for chat online indicator - Add needsFollowup flag to ucastnici table - Add getTodayCalendarCount endpoint for calendar badge - Add company reminders to calendar data - Enhance company search to include phone and contacts - Update routes to allow team_leader access to kurzy, services, timesheets Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { db } from '../config/database.js';
|
||||
import { events, eventUsers, users, todos, todoUsers } from '../db/schema.js';
|
||||
import { eq, and, gte, lt, desc, inArray } from 'drizzle-orm';
|
||||
import { events, eventUsers, users, todos, todoUsers, companyReminders, companyUsers, companies } from '../db/schema.js';
|
||||
import { eq, and, gte, lt, desc, inArray, sql, ne } from 'drizzle-orm';
|
||||
import { NotFoundError } from '../utils/errors.js';
|
||||
|
||||
/**
|
||||
@@ -202,9 +202,56 @@ export const getCalendarData = async (year, month, userId, isAdmin) => {
|
||||
updatedAt: todo.updatedAt instanceof Date ? todo.updatedAt.toISOString() : todo.updatedAt,
|
||||
}));
|
||||
|
||||
// Get company reminders for month
|
||||
let accessibleCompanyIds = null;
|
||||
if (!isAdmin) {
|
||||
// Member sees only reminders from companies they are assigned to
|
||||
const userCompanies = await db
|
||||
.select({ companyId: companyUsers.companyId })
|
||||
.from(companyUsers)
|
||||
.where(eq(companyUsers.userId, userId));
|
||||
accessibleCompanyIds = userCompanies.map((row) => row.companyId);
|
||||
}
|
||||
|
||||
let monthReminders = [];
|
||||
if (isAdmin || (accessibleCompanyIds && accessibleCompanyIds.length > 0)) {
|
||||
const reminderConditions = [
|
||||
gte(companyReminders.dueDate, startOfMonth),
|
||||
lt(companyReminders.dueDate, endOfMonth),
|
||||
eq(companyReminders.isChecked, false),
|
||||
];
|
||||
|
||||
if (!isAdmin && accessibleCompanyIds) {
|
||||
reminderConditions.push(inArray(companyReminders.companyId, accessibleCompanyIds));
|
||||
}
|
||||
|
||||
monthReminders = await db
|
||||
.select({
|
||||
id: companyReminders.id,
|
||||
companyId: companyReminders.companyId,
|
||||
description: companyReminders.description,
|
||||
dueDate: companyReminders.dueDate,
|
||||
isChecked: companyReminders.isChecked,
|
||||
createdAt: companyReminders.createdAt,
|
||||
companyName: companies.name,
|
||||
})
|
||||
.from(companyReminders)
|
||||
.innerJoin(companies, eq(companyReminders.companyId, companies.id))
|
||||
.where(and(...reminderConditions))
|
||||
.orderBy(desc(companyReminders.dueDate));
|
||||
}
|
||||
|
||||
// Format reminders for calendar
|
||||
const formattedReminders = monthReminders.map((reminder) => ({
|
||||
...reminder,
|
||||
dueDate: reminder.dueDate instanceof Date ? reminder.dueDate.toISOString() : reminder.dueDate,
|
||||
createdAt: reminder.createdAt instanceof Date ? reminder.createdAt.toISOString() : reminder.createdAt,
|
||||
}));
|
||||
|
||||
return {
|
||||
events: formattedEvents,
|
||||
todos: formattedTodos,
|
||||
reminders: formattedReminders,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -352,3 +399,91 @@ export const deleteEvent = async (eventId) => {
|
||||
|
||||
return { success: true, message: 'Event bol zmazaný' };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get count of events and todos for today
|
||||
* Used for calendar badge in sidebar
|
||||
*/
|
||||
export const getTodayCalendarCount = async (userId, isAdmin) => {
|
||||
const today = new Date();
|
||||
const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||||
const endOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
|
||||
|
||||
// Get event count for today
|
||||
let eventCount;
|
||||
if (isAdmin) {
|
||||
const eventResult = await db
|
||||
.select({ count: sql`count(*)::int` })
|
||||
.from(events)
|
||||
.where(
|
||||
and(
|
||||
gte(events.start, startOfDay),
|
||||
lt(events.start, endOfDay)
|
||||
)
|
||||
);
|
||||
eventCount = eventResult[0]?.count || 0;
|
||||
} else {
|
||||
const accessibleEventIds = await getAccessibleEventIds(userId);
|
||||
if (accessibleEventIds.length === 0) {
|
||||
eventCount = 0;
|
||||
} else {
|
||||
const eventResult = await db
|
||||
.select({ count: sql`count(*)::int` })
|
||||
.from(events)
|
||||
.where(
|
||||
and(
|
||||
gte(events.start, startOfDay),
|
||||
lt(events.start, endOfDay),
|
||||
inArray(events.id, accessibleEventIds)
|
||||
)
|
||||
);
|
||||
eventCount = eventResult[0]?.count || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Get todo count for today
|
||||
let todoCount;
|
||||
if (isAdmin) {
|
||||
const todoResult = await db
|
||||
.select({ count: sql`count(*)::int` })
|
||||
.from(todos)
|
||||
.where(
|
||||
and(
|
||||
gte(todos.dueDate, startOfDay),
|
||||
lt(todos.dueDate, endOfDay),
|
||||
ne(todos.status, 'completed')
|
||||
)
|
||||
);
|
||||
todoCount = todoResult[0]?.count || 0;
|
||||
} else {
|
||||
const userTodos = await db
|
||||
.select({ todoId: todoUsers.todoId })
|
||||
.from(todoUsers)
|
||||
.where(eq(todoUsers.userId, userId));
|
||||
|
||||
const todoIds = userTodos.map((row) => row.todoId);
|
||||
|
||||
if (todoIds.length === 0) {
|
||||
todoCount = 0;
|
||||
} else {
|
||||
const todoResult = await db
|
||||
.select({ count: sql`count(*)::int` })
|
||||
.from(todos)
|
||||
.where(
|
||||
and(
|
||||
gte(todos.dueDate, startOfDay),
|
||||
lt(todos.dueDate, endOfDay),
|
||||
ne(todos.status, 'completed'),
|
||||
inArray(todos.id, todoIds)
|
||||
)
|
||||
);
|
||||
todoCount = todoResult[0]?.count || 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
eventCount,
|
||||
todoCount,
|
||||
totalCount: eventCount + todoCount,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user