|
|
|
|
@ -0,0 +1,179 @@
|
|
|
|
|
import { TaskService } from '../../tasks/service';
|
|
|
|
|
import { GroupSyncService } from '../../group-sync';
|
|
|
|
|
import { ContactsService } from '../../contacts';
|
|
|
|
|
import { ICONS } from '../../../utils/icons';
|
|
|
|
|
import { codeId, formatDDMM, bold, italic } from '../../../utils/formatting';
|
|
|
|
|
import { SCOPE_ALIASES, todayYMD } from '../shared';
|
|
|
|
|
|
|
|
|
|
type Ctx = {
|
|
|
|
|
sender: string;
|
|
|
|
|
groupId: string;
|
|
|
|
|
message: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type Msg = {
|
|
|
|
|
recipient: string;
|
|
|
|
|
message: string;
|
|
|
|
|
mentions?: string[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export async function handleVer(context: Ctx): Promise<Msg[]> {
|
|
|
|
|
const trimmed = (context.message || '').trim();
|
|
|
|
|
const tokens = trimmed.split(/\s+/);
|
|
|
|
|
const rawAction = (tokens[1] || '').toLowerCase();
|
|
|
|
|
|
|
|
|
|
const scopeRaw = (tokens[2] || '').toLowerCase();
|
|
|
|
|
const scope = scopeRaw
|
|
|
|
|
? (SCOPE_ALIASES[scopeRaw] || scopeRaw)
|
|
|
|
|
: ((rawAction === 'mias' || rawAction === 'mías') ? 'mis' : ((rawAction === 'todas' || rawAction === 'todos') ? 'todos' : 'todos'));
|
|
|
|
|
|
|
|
|
|
const LIMIT = 10;
|
|
|
|
|
const today = todayYMD();
|
|
|
|
|
|
|
|
|
|
if (scope === 'todos') {
|
|
|
|
|
const sections: string[] = [];
|
|
|
|
|
|
|
|
|
|
// Encabezado fijo para la sección de tareas del usuario
|
|
|
|
|
sections.push(bold('Tus tareas'));
|
|
|
|
|
|
|
|
|
|
// Tus tareas (mis)
|
|
|
|
|
const myItems = TaskService.listUserPending(context.sender, LIMIT);
|
|
|
|
|
if (myItems.length > 0) {
|
|
|
|
|
// Agrupar por grupo como en "ver mis"
|
|
|
|
|
const byGroup = new Map<string, typeof myItems>();
|
|
|
|
|
for (const t of myItems) {
|
|
|
|
|
const key = t.group_id || '(sin grupo)';
|
|
|
|
|
const arr = byGroup.get(key) || [];
|
|
|
|
|
arr.push(t);
|
|
|
|
|
byGroup.set(key, arr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const [groupId, arr] of byGroup.entries()) {
|
|
|
|
|
const groupName =
|
|
|
|
|
(groupId && GroupSyncService.activeGroupsCache.get(groupId)) ||
|
|
|
|
|
(groupId && groupId !== '(sin grupo)' ? groupId : 'Sin grupo');
|
|
|
|
|
|
|
|
|
|
sections.push(groupName);
|
|
|
|
|
const rendered = await Promise.all(arr.map(async (t) => {
|
|
|
|
|
const names = await Promise.all(
|
|
|
|
|
(t.assignees || []).map(async (uid) => (await ContactsService.getDisplayName(uid)) || uid)
|
|
|
|
|
);
|
|
|
|
|
const owner =
|
|
|
|
|
(t.assignees?.length || 0) === 0
|
|
|
|
|
? `${ICONS.unassigned} sin responsable`
|
|
|
|
|
: `${t.assignees!.length > 1 ? '👥' : '👤'} ${names.join(', ')}`;
|
|
|
|
|
const isOverdue = t.due_date ? t.due_date < today : false;
|
|
|
|
|
const datePart = t.due_date ? ` — ${isOverdue ? `${ICONS.warn} ` : ''}${ICONS.date} ${formatDDMM(t.due_date)}` : '';
|
|
|
|
|
return `- ${codeId(t.id, t.display_code)} ${t.description || '(sin descripción)'}${datePart} — ${owner}`;
|
|
|
|
|
}));
|
|
|
|
|
sections.push(...rendered);
|
|
|
|
|
sections.push('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Quitar línea en blanco final si procede
|
|
|
|
|
if (sections.length > 0 && sections[sections.length - 1] === '') {
|
|
|
|
|
sections.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const totalMy = TaskService.countUserPending(context.sender);
|
|
|
|
|
if (totalMy > myItems.length) {
|
|
|
|
|
sections.push(`… y ${totalMy - myItems.length} más`);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
sections.push(italic('_No tienes tareas pendientes._'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// En DM: usar membresía real (snapshot fresca) para incluir "sin responsable" por grupo
|
|
|
|
|
const memberGroups = GroupSyncService.getFreshMemberGroupsForUser(context.sender);
|
|
|
|
|
if (memberGroups.length > 0) {
|
|
|
|
|
const perGroup = TaskService.listUnassignedByGroups(memberGroups, LIMIT);
|
|
|
|
|
for (const gid of perGroup.keys()) {
|
|
|
|
|
const unassigned = perGroup.get(gid)!;
|
|
|
|
|
const groupName =
|
|
|
|
|
(gid && GroupSyncService.activeGroupsCache.get(gid)) ||
|
|
|
|
|
gid;
|
|
|
|
|
|
|
|
|
|
if (unassigned.length > 0) {
|
|
|
|
|
if (sections.length && sections[sections.length - 1] !== '') sections.push('');
|
|
|
|
|
sections.push(`${groupName} — Sin responsable`);
|
|
|
|
|
const renderedUnassigned = unassigned.map((t) => {
|
|
|
|
|
const isOverdue = t.due_date ? t.due_date < today : false;
|
|
|
|
|
const datePart = t.due_date ? ` — ${isOverdue ? `${ICONS.warn} ` : ''}${ICONS.date} ${formatDDMM(t.due_date)}` : '';
|
|
|
|
|
return `- ${codeId(t.id, t.display_code)} ${t.description || '(sin descripción)'}${datePart} — ${ICONS.unassigned}`;
|
|
|
|
|
});
|
|
|
|
|
sections.push(...renderedUnassigned);
|
|
|
|
|
|
|
|
|
|
const totalUnassigned = TaskService.countGroupUnassigned(gid);
|
|
|
|
|
if (totalUnassigned > unassigned.length) {
|
|
|
|
|
sections.push(`… y ${totalUnassigned - unassigned.length} más`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Si no hay snapshot fresca de membresía, nota instructiva
|
|
|
|
|
sections.push('ℹ️ Para ver tareas sin responsable, escribe por privado `/t todas` o usa `/t web`.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: sections.join('\n')
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ver mis
|
|
|
|
|
const items = TaskService.listUserPending(context.sender, LIMIT);
|
|
|
|
|
if (items.length === 0) {
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: italic('No tienes tareas pendientes.')
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const total = TaskService.countUserPending(context.sender);
|
|
|
|
|
|
|
|
|
|
// Agrupar por grupo
|
|
|
|
|
const byGroup = new Map<string, typeof items>();
|
|
|
|
|
for (const t of items) {
|
|
|
|
|
const key = t.group_id || '(sin grupo)';
|
|
|
|
|
const arr = byGroup.get(key) || [];
|
|
|
|
|
arr.push(t);
|
|
|
|
|
byGroup.set(key, arr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sections: string[] = [bold('Tus tareas')];
|
|
|
|
|
for (const [groupId, arr] of byGroup.entries()) {
|
|
|
|
|
const groupName =
|
|
|
|
|
(groupId && GroupSyncService.activeGroupsCache.get(groupId)) ||
|
|
|
|
|
(groupId && groupId !== '(sin grupo)' ? groupId : 'Sin grupo');
|
|
|
|
|
|
|
|
|
|
sections.push(groupName);
|
|
|
|
|
const rendered = await Promise.all(arr.map(async (t) => {
|
|
|
|
|
const names = await Promise.all(
|
|
|
|
|
(t.assignees || []).map(async (uid) => (await ContactsService.getDisplayName(uid)) || uid)
|
|
|
|
|
);
|
|
|
|
|
const owner =
|
|
|
|
|
(t.assignees?.length || 0) === 0
|
|
|
|
|
? `${ICONS.unassigned}`
|
|
|
|
|
: `${t.assignees!.length > 1 ? '👥' : '👤'} ${names.join(', ')}`;
|
|
|
|
|
const isOverdue = t.due_date ? t.due_date < today : false;
|
|
|
|
|
const datePart = t.due_date ? ` — ${isOverdue ? `${ICONS.warn} ` : ''}${ICONS.date} ${formatDDMM(t.due_date)}` : '';
|
|
|
|
|
return `- ${codeId(t.id, t.display_code)} ${t.description || '(sin descripción)'}${datePart} — ${owner}`;
|
|
|
|
|
}));
|
|
|
|
|
sections.push(...rendered);
|
|
|
|
|
sections.push('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Quitar línea en blanco final si procede
|
|
|
|
|
if (sections.length > 0 && sections[sections.length - 1] === '') {
|
|
|
|
|
sections.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (total > items.length) {
|
|
|
|
|
sections.push(`… y ${total - items.length} más`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [{
|
|
|
|
|
recipient: context.sender,
|
|
|
|
|
message: sections.join('\n')
|
|
|
|
|
}];
|
|
|
|
|
}
|