Files
oikos/public/styles/user-multi-select.css
T
Ulas Kalayci 2a48fb7af0 feat: multi-person assignment for tasks and calendar events
- DB migration v32: task_assignments and event_assignments join tables
  with CASCADE delete; existing assigned_to data migrated automatically
- Tasks API: accepts assigned_to as array, returns assigned_users[]
  with json_group_array; filter uses EXISTS on task_assignments
- Calendar API: same pattern via event_assignments; serializeEvent
  includes assigned_users array
- Recurring task completion copies all assignments to the new instance
- Frontend: shared UserMultiSelect component with avatar stack display
  (renderAvatarStack, renderUserMultiSelect, getSelectedUserIds,
  bindUserMultiSelect); tasks.js and calendar.js use it in modals
  and card/agenda views
- CSS: user-multi-select.css with avatar-stack and user-ms classes
- 14 new tests covering CRUD, JSON aggregation, EXISTS filter,
  and CASCADE behavior for both task and event assignments

Closes #125
2026-05-06 10:04:41 +02:00

103 lines
2.3 KiB
CSS

/* ------------------------------------------------------------------ */
/* Avatar-Stack (Mehrfach-Zuweisung Anzeige) */
/* ------------------------------------------------------------------ */
.avatar-stack {
display: inline-flex;
align-items: center;
flex-direction: row-reverse;
}
.avatar-stack__item {
border-radius: var(--radius-full);
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: var(--font-weight-bold);
color: var(--color-text-on-accent);
border: 2px solid var(--color-surface);
flex-shrink: 0;
margin-left: -6px;
}
.avatar-stack__item:last-child {
margin-left: 0;
}
.avatar-stack__overflow {
background-color: var(--color-text-secondary);
}
/* ------------------------------------------------------------------ */
/* User Multi-Select Widget */
/* ------------------------------------------------------------------ */
.user-ms {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.user-ms__options {
display: flex;
flex-direction: column;
gap: var(--space-1);
max-height: 180px;
overflow-y: auto;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: var(--space-1);
background: var(--color-surface);
}
.user-ms__option {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-sm);
cursor: pointer;
transition: background var(--transition-fast);
user-select: none;
}
.user-ms__option:hover {
background: var(--color-surface-alt);
}
.user-ms__checkbox {
accent-color: var(--color-accent);
width: 16px;
height: 16px;
flex-shrink: 0;
cursor: pointer;
}
.user-ms__avatar {
width: 26px;
height: 26px;
border-radius: var(--radius-full);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: var(--text-xs);
font-weight: var(--font-weight-bold);
color: var(--color-text-on-accent);
flex-shrink: 0;
}
.user-ms__avatar--none {
background-color: var(--color-border);
color: var(--color-text-secondary);
}
.user-ms__name {
font-size: var(--text-sm);
color: var(--color-text-primary);
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}