mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 08:41:10 +02:00
feat(manacore/web): show tags in detail views with click-to-remove
Display assigned tags as colored pills in todo, calendar, contacts DetailViews. Clicking a tag pill removes it from the item. Hover turns the X icon red for clear affordance. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
794424d6ad
commit
019f3eb9fd
3 changed files with 215 additions and 2 deletions
|
|
@ -6,9 +6,10 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import { eventsStore } from '../stores/events.svelte';
|
||||
import { Trash, MapPin, Clock } from '@manacore/shared-icons';
|
||||
import { Trash, MapPin, Clock, X } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { LocalEvent } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
let eventId = $derived(params.eventId as string);
|
||||
|
|
@ -26,6 +27,18 @@
|
|||
|
||||
let focused = $state(false);
|
||||
|
||||
const tagsQuery = useAllTags();
|
||||
let allTags = $derived(tagsQuery.value ?? []);
|
||||
let eventTags = $derived(getTagsByIds(allTags, event?.tagIds ?? []));
|
||||
|
||||
async function removeTag(tagId: string) {
|
||||
const current = event?.tagIds ?? [];
|
||||
await eventsStore.updateTagIds(
|
||||
eventId,
|
||||
current.filter((id) => id !== tagId)
|
||||
);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
eventId; // track
|
||||
confirmDelete = false;
|
||||
|
|
@ -145,6 +158,26 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
{#if eventTags.length > 0}
|
||||
<div class="section">
|
||||
<span class="section-label">Tags</span>
|
||||
<div class="tags-list">
|
||||
{#each eventTags as tag (tag.id)}
|
||||
<button
|
||||
class="tag-pill"
|
||||
style="--tag-color: {tag.color}"
|
||||
onclick={() => removeTag(tag.id)}
|
||||
>
|
||||
<span class="tag-dot" style="background: {tag.color}"></span>
|
||||
{tag.name}
|
||||
<X size={10} />
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Description -->
|
||||
<div class="section">
|
||||
<span class="section-label">Beschreibung</span>
|
||||
|
|
@ -298,6 +331,42 @@
|
|||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.tags-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.tag-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
border: none;
|
||||
background: color-mix(in srgb, var(--tag-color) 12%, transparent);
|
||||
font-size: 0.6875rem;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.tag-pill:hover {
|
||||
background: color-mix(in srgb, var(--tag-color) 20%, transparent);
|
||||
color: #ef4444;
|
||||
}
|
||||
:global(.dark) .tag-pill {
|
||||
background: color-mix(in srgb, var(--tag-color) 18%, transparent);
|
||||
color: #9ca3af;
|
||||
}
|
||||
:global(.dark) .tag-pill:hover {
|
||||
background: color-mix(in srgb, var(--tag-color) 28%, transparent);
|
||||
color: #ef4444;
|
||||
}
|
||||
.tag-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 9999px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section-label {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@
|
|||
MapPin,
|
||||
Briefcase,
|
||||
Globe,
|
||||
X,
|
||||
} from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { LocalContact } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
let contactId = $derived(params.contactId as string);
|
||||
|
|
@ -40,6 +42,18 @@
|
|||
let editWebsite = $state('');
|
||||
let editNotes = $state('');
|
||||
|
||||
const tagsQuery = useAllTags();
|
||||
let allTags = $derived(tagsQuery.value ?? []);
|
||||
let contactTags = $derived(getTagsByIds(allTags, contact?.tagIds ?? []));
|
||||
|
||||
async function removeTag(tagId: string) {
|
||||
const current = contact?.tagIds ?? [];
|
||||
await contactsStore.updateTagIds(
|
||||
contactId,
|
||||
current.filter((id) => id !== tagId)
|
||||
);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
contactId; // track
|
||||
confirmDelete = false;
|
||||
|
|
@ -255,6 +269,26 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
{#if contactTags.length > 0}
|
||||
<div class="section">
|
||||
<span class="section-label">Tags</span>
|
||||
<div class="tags-list">
|
||||
{#each contactTags as tag (tag.id)}
|
||||
<button
|
||||
class="tag-pill"
|
||||
style="--tag-color: {tag.color}"
|
||||
onclick={() => removeTag(tag.id)}
|
||||
>
|
||||
<span class="tag-dot" style="background: {tag.color}"></span>
|
||||
{tag.name}
|
||||
<X size={10} />
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="section">
|
||||
<span class="section-label">Notizen</span>
|
||||
|
|
@ -447,6 +481,42 @@
|
|||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.tags-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.tag-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
border: none;
|
||||
background: color-mix(in srgb, var(--tag-color) 12%, transparent);
|
||||
font-size: 0.6875rem;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.tag-pill:hover {
|
||||
background: color-mix(in srgb, var(--tag-color) 20%, transparent);
|
||||
color: #ef4444;
|
||||
}
|
||||
:global(.dark) .tag-pill {
|
||||
background: color-mix(in srgb, var(--tag-color) 18%, transparent);
|
||||
color: #9ca3af;
|
||||
}
|
||||
:global(.dark) .tag-pill:hover {
|
||||
background: color-mix(in srgb, var(--tag-color) 28%, transparent);
|
||||
color: #ef4444;
|
||||
}
|
||||
.tag-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 9999px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section-label {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import { tasksStore } from '../stores/tasks.svelte';
|
||||
import { Check, Trash } from '@manacore/shared-icons';
|
||||
import { Check, Trash, X } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { LocalTask, TaskPriority } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
let taskId = $derived(params.taskId as string);
|
||||
|
|
@ -25,6 +26,23 @@
|
|||
// Track whether user is actively editing to prevent overwrite from liveQuery
|
||||
let focused = $state(false);
|
||||
|
||||
const tagsQuery = useAllTags();
|
||||
let allTags = $derived(tagsQuery.value ?? []);
|
||||
|
||||
function getTaskTagIds(): string[] {
|
||||
return ((task?.metadata as Record<string, unknown>)?.labelIds as string[]) ?? [];
|
||||
}
|
||||
|
||||
let taskTags = $derived(getTagsByIds(allTags, getTaskTagIds()));
|
||||
|
||||
async function removeTag(tagId: string) {
|
||||
const current = getTaskTagIds();
|
||||
await tasksStore.updateLabels(
|
||||
taskId,
|
||||
current.filter((id) => id !== tagId)
|
||||
);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
taskId; // track
|
||||
confirmDelete = false;
|
||||
|
|
@ -152,6 +170,26 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
{#if taskTags.length > 0}
|
||||
<div class="section">
|
||||
<span class="section-label">Tags</span>
|
||||
<div class="tags-list">
|
||||
{#each taskTags as tag (tag.id)}
|
||||
<button
|
||||
class="tag-pill"
|
||||
style="--tag-color: {tag.color}"
|
||||
onclick={() => removeTag(tag.id)}
|
||||
>
|
||||
<span class="tag-dot" style="background: {tag.color}"></span>
|
||||
{tag.name}
|
||||
<X size={10} />
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Description -->
|
||||
<div class="section">
|
||||
<span class="section-label">Beschreibung</span>
|
||||
|
|
@ -349,6 +387,42 @@
|
|||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.tags-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.tag-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
border: none;
|
||||
background: color-mix(in srgb, var(--tag-color) 12%, transparent);
|
||||
font-size: 0.6875rem;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.tag-pill:hover {
|
||||
background: color-mix(in srgb, var(--tag-color) 20%, transparent);
|
||||
color: #ef4444;
|
||||
}
|
||||
:global(.dark) .tag-pill {
|
||||
background: color-mix(in srgb, var(--tag-color) 18%, transparent);
|
||||
color: #9ca3af;
|
||||
}
|
||||
:global(.dark) .tag-pill:hover {
|
||||
background: color-mix(in srgb, var(--tag-color) 28%, transparent);
|
||||
color: #ef4444;
|
||||
}
|
||||
.tag-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 9999px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section-label {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue