refactor(contacts,todo): extract shared utilities, eliminate duplication

Contacts:
- Extract getDisplayName() + getInitials() to lib/utils/contact-display.ts
  (was duplicated across 7 files)
- Export UNKNOWN_CONTACT_NAME constant

Todo:
- Extract getSubtaskProgress() to lib/utils/task-helpers.ts
  (was duplicated in TaskItem + KanbanTaskCard)
- Add formatDateForInput() + dateInputToISO() to date-display.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-31 13:10:55 +02:00
parent 52e09e4ac0
commit bc1788941f
12 changed files with 67 additions and 106 deletions

View file

@ -9,8 +9,10 @@
} from '@todo/shared';
import type { ContactReference, ContactOrManual } from '@manacore/shared-types';
import { STATUS_OPTIONS, RECURRENCE_OPTIONS } from '@todo/shared';
import { isToday, isPast } from 'date-fns';
import { format, isToday, isPast } from 'date-fns';
import { de } from 'date-fns/locale';
import { formatDueDate } from '$lib/utils/date-display';
import { getSubtaskProgress } from '$lib/utils/task-helpers';
import { getContext } from 'svelte';
import type { Project } from '@todo/shared';
import { getActiveProjects, getProjectColor } from '$lib/data/task-queries';
@ -314,11 +316,7 @@
});
// Subtasks progress
let subtaskProgress = $derived(() => {
if (!task.subtasks || task.subtasks.length === 0) return null;
const completed = task.subtasks.filter((s) => s.isCompleted).length;
return `${completed}/${task.subtasks.length}`;
});
let subtaskProgress = $derived(() => getSubtaskProgress(task.subtasks));
// Long press to expand (mobile)
let longPressTimer: ReturnType<typeof setTimeout> | null = null;

View file

@ -2,6 +2,7 @@
import type { Task } from '@todo/shared';
import { isToday, isPast } from 'date-fns';
import { formatDueDate } from '$lib/utils/date-display';
import { getSubtaskProgress } from '$lib/utils/task-helpers';
import { ConfirmationModal, ContactAvatar } from '@manacore/shared-ui';
import TaskEditModal from '../TaskEditModal.svelte';
import {
@ -53,11 +54,7 @@
});
// Subtasks progress
let subtaskProgress = $derived(() => {
if (!task.subtasks || task.subtasks.length === 0) return null;
const completed = task.subtasks.filter((s) => s.isCompleted).length;
return `${completed}/${task.subtasks.length}`;
});
let subtaskProgress = $derived(() => getSubtaskProgress(task.subtasks));
// Click to open modal
function handleCardClick(e: MouseEvent) {

View file

@ -10,3 +10,20 @@ export function formatDueDate(date: Date): string {
if (isTomorrow(date)) return 'Morgen';
return format(date, 'dd. MMM', { locale: de });
}
/**
* Format a date string for use in an HTML date input (yyyy-MM-dd).
* Returns empty string if no date is provided.
*/
export function formatDateForInput(dateString?: string | null): string {
if (!dateString) return '';
return format(new Date(dateString), 'yyyy-MM-dd');
}
/**
* Convert an HTML date input value (yyyy-MM-dd) to an ISO string.
* Returns null if the input is empty.
*/
export function dateInputToISO(dateString: string): string | null {
return dateString ? new Date(dateString).toISOString() : null;
}

View file

@ -0,0 +1,9 @@
/**
* Calculate subtask progress as a string like "2/5".
* Returns null if there are no subtasks.
*/
export function getSubtaskProgress(subtasks?: { isCompleted: boolean }[]): string | null {
if (!subtasks || subtasks.length === 0) return null;
const completed = subtasks.filter((s) => s.isCompleted).length;
return `${completed}/${subtasks.length}`;
}