mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
chore(manacore/web): remove dead code from todo module refactor
Delete components and stores that are no longer imported after the pages system and workbench refactor: - MinimizedTabs.svelte (replaced by inline tabs in todo +page) - TagStrip.svelte (old filter strip, unused) - TodoToolbar.svelte (old Inbox/Today/Upcoming tabs, unused) - minimized-pages.svelte.ts store (unused) - view.svelte.ts store (only used by deleted components) - AppRow.svelte (old home page grid, replaced by workbench) - ActivityFeed.svelte (old home page feed, replaced by workbench) - Remove viewStore + minimizedPagesStore from todo/index.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e7ae444b18
commit
39af8f8480
8 changed files with 0 additions and 759 deletions
|
|
@ -1,221 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { db } from '$lib/data/database';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
maxItems?: number;
|
|
||||||
locale?: 'de' | 'en';
|
|
||||||
}
|
|
||||||
|
|
||||||
let { maxItems = 8, locale = 'de' }: Props = $props();
|
|
||||||
|
|
||||||
interface FeedItem {
|
|
||||||
id: string;
|
|
||||||
type: 'task' | 'event' | 'contact';
|
|
||||||
title: string;
|
|
||||||
subtitle?: string;
|
|
||||||
timestamp: string;
|
|
||||||
color: string;
|
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query recent completed tasks
|
|
||||||
let recentTasks = $state<FeedItem[]>([]);
|
|
||||||
let upcomingEvents = $state<FeedItem[]>([]);
|
|
||||||
let recentContacts = $state<FeedItem[]>([]);
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
loadFeed();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function loadFeed() {
|
|
||||||
try {
|
|
||||||
// Completed tasks from today
|
|
||||||
const today = new Date();
|
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
const todayStr = today.toISOString();
|
|
||||||
|
|
||||||
const tasks = await db.table('tasks').toArray();
|
|
||||||
recentTasks = tasks
|
|
||||||
.filter((t) => t.isCompleted && t.completedAt && t.completedAt >= todayStr)
|
|
||||||
.sort((a, b) => (b.completedAt || '').localeCompare(a.completedAt || ''))
|
|
||||||
.slice(0, 5)
|
|
||||||
.map((t) => ({
|
|
||||||
id: `task-${t.id}`,
|
|
||||||
type: 'task' as const,
|
|
||||||
title: t.title,
|
|
||||||
subtitle: locale === 'de' ? 'Erledigt' : 'Completed',
|
|
||||||
timestamp: t.completedAt || t.updatedAt,
|
|
||||||
color: '#22c55e',
|
|
||||||
icon: '\u2705',
|
|
||||||
}));
|
|
||||||
} catch {
|
|
||||||
recentTasks = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Upcoming events (next 24h)
|
|
||||||
const now = new Date().toISOString();
|
|
||||||
const tomorrow = new Date(Date.now() + 86400000).toISOString();
|
|
||||||
|
|
||||||
const events = await db.table('events').toArray();
|
|
||||||
upcomingEvents = events
|
|
||||||
.filter((e) => e.startDate >= now && e.startDate <= tomorrow)
|
|
||||||
.sort((a, b) => a.startDate.localeCompare(b.startDate))
|
|
||||||
.slice(0, 5)
|
|
||||||
.map((e) => ({
|
|
||||||
id: `event-${e.id}`,
|
|
||||||
type: 'event' as const,
|
|
||||||
title: e.title,
|
|
||||||
subtitle: formatTime(e.startDate),
|
|
||||||
timestamp: e.startDate,
|
|
||||||
color: e.color || '#6366f1',
|
|
||||||
icon: '\uD83D\uDCC5',
|
|
||||||
}));
|
|
||||||
} catch {
|
|
||||||
upcomingEvents = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Recently added contacts
|
|
||||||
const contacts = await db.table('contacts').toArray();
|
|
||||||
recentContacts = contacts
|
|
||||||
.filter((c) => !c.isArchived)
|
|
||||||
.sort((a, b) => (b.createdAt || '').localeCompare(a.createdAt || ''))
|
|
||||||
.slice(0, 3)
|
|
||||||
.map((c) => {
|
|
||||||
const name = [c.firstName, c.lastName].filter(Boolean).join(' ') || c.email || '?';
|
|
||||||
return {
|
|
||||||
id: `contact-${c.id}`,
|
|
||||||
type: 'contact' as const,
|
|
||||||
title: name,
|
|
||||||
subtitle: c.company || undefined,
|
|
||||||
timestamp: c.createdAt,
|
|
||||||
color: '#8b5cf6',
|
|
||||||
icon: '\uD83D\uDC64',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
recentContacts = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let feedItems = $derived(
|
|
||||||
[...recentTasks, ...upcomingEvents, ...recentContacts]
|
|
||||||
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
|
||||||
.slice(0, maxItems)
|
|
||||||
);
|
|
||||||
|
|
||||||
function formatTime(isoString: string): string {
|
|
||||||
try {
|
|
||||||
const date = new Date(isoString);
|
|
||||||
return date.toLocaleTimeString(locale === 'de' ? 'de-DE' : 'en-US', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatRelative(isoString: string): string {
|
|
||||||
try {
|
|
||||||
const diff = Date.now() - new Date(isoString).getTime();
|
|
||||||
const mins = Math.floor(diff / 60000);
|
|
||||||
if (mins < 1) return locale === 'de' ? 'gerade eben' : 'just now';
|
|
||||||
if (mins < 60) return `${mins}m`;
|
|
||||||
const hrs = Math.floor(mins / 60);
|
|
||||||
if (hrs < 24) return `${hrs}h`;
|
|
||||||
return `${Math.floor(hrs / 24)}d`;
|
|
||||||
} catch {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if feedItems.length > 0}
|
|
||||||
<section class="feed-section">
|
|
||||||
<h2 class="feed-title">{locale === 'de' ? 'Aktivität' : 'Activity'}</h2>
|
|
||||||
<div class="feed-list">
|
|
||||||
{#each feedItems as item (item.id)}
|
|
||||||
<div class="feed-item">
|
|
||||||
<span class="feed-icon">{item.icon}</span>
|
|
||||||
<div class="feed-content">
|
|
||||||
<span class="feed-item-title">{item.title}</span>
|
|
||||||
{#if item.subtitle}
|
|
||||||
<span class="feed-item-sub">{item.subtitle}</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<span class="feed-time">{formatRelative(item.timestamp)}</span>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.feed-section {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-title {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: hsl(var(--muted-foreground, 0 0% 45%));
|
|
||||||
margin: 0 0 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.625rem;
|
|
||||||
padding: 0.5rem 0.625rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
transition: background 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-item:hover {
|
|
||||||
background: hsl(var(--muted, 0 0% 96%) / 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-icon {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-item-title {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: hsl(var(--foreground, 0 0% 9%));
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-item-sub {
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
color: hsl(var(--muted-foreground, 0 0% 45%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-time {
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
color: hsl(var(--muted-foreground, 0 0% 45%));
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { ManaApp, AppIconId } from '@manacore/shared-branding';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
apps: ManaApp[];
|
|
||||||
title: string;
|
|
||||||
emptyText?: string;
|
|
||||||
showPin?: boolean;
|
|
||||||
onAppClick: (app: ManaApp) => void;
|
|
||||||
onTogglePin?: (appId: string) => void;
|
|
||||||
pinnedIds?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
let {
|
|
||||||
apps,
|
|
||||||
title,
|
|
||||||
emptyText,
|
|
||||||
showPin = false,
|
|
||||||
onAppClick,
|
|
||||||
onTogglePin,
|
|
||||||
pinnedIds = [],
|
|
||||||
}: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if apps.length > 0}
|
|
||||||
<section class="app-row-section">
|
|
||||||
<h2 class="row-title">{title}</h2>
|
|
||||||
<div class="row-scroll">
|
|
||||||
{#each apps as app (app.id)}
|
|
||||||
<button class="row-card" style="--app-color: {app.color};" onclick={() => onAppClick(app)}>
|
|
||||||
<div class="row-card-icon">
|
|
||||||
{#if app.icon}
|
|
||||||
<img src={app.icon} alt={app.name} class="row-icon" />
|
|
||||||
{:else}
|
|
||||||
<span class="row-icon-letter" style="color: {app.color};">{app.name.charAt(0)}</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<span class="row-card-name">{app.name}</span>
|
|
||||||
{#if showPin && onTogglePin}
|
|
||||||
<button
|
|
||||||
class="pin-btn"
|
|
||||||
class:pinned={pinnedIds.includes(app.id)}
|
|
||||||
onclick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onTogglePin(app.id);
|
|
||||||
}}
|
|
||||||
title={pinnedIds.includes(app.id)
|
|
||||||
? 'Aus Favoriten entfernen'
|
|
||||||
: 'Zu Favoriten hinzufügen'}
|
|
||||||
>
|
|
||||||
{pinnedIds.includes(app.id) ? '\u2605' : '\u2606'}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{:else if emptyText}
|
|
||||||
<section class="app-row-section">
|
|
||||||
<h2 class="row-title">{title}</h2>
|
|
||||||
<p class="row-empty">{emptyText}</p>
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.app-row-section {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-title {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: hsl(var(--muted-foreground, 0 0% 45%));
|
|
||||||
margin: 0 0 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-scroll {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.625rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding-bottom: 0.25rem;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-scroll::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.375rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
border: 1px solid hsl(var(--border, 0 0% 90%));
|
|
||||||
background: hsl(var(--card, 0 0% 100%));
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.15s;
|
|
||||||
min-width: 5.5rem;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-card:hover {
|
|
||||||
border-color: color-mix(in srgb, var(--app-color) 40%, hsl(var(--border, 0 0% 90%)));
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-card-icon {
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-icon {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-icon-letter {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-card-name {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: hsl(var(--foreground, 0 0% 9%));
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.25rem;
|
|
||||||
right: 0.25rem;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
opacity: 0.3;
|
|
||||||
transition: opacity 0.15s;
|
|
||||||
padding: 0.125rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin-btn:hover,
|
|
||||||
.pin-btn.pinned {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin-btn.pinned {
|
|
||||||
color: #eab308;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-empty {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
color: hsl(var(--muted-foreground, 0 0% 45%));
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { minimizedPagesStore } from '../stores/minimized-pages.svelte';
|
|
||||||
import { X, ArrowsOut } from '@manacore/shared-icons';
|
|
||||||
|
|
||||||
let pages = $derived(minimizedPagesStore.pages);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if pages.length > 0}
|
|
||||||
<div
|
|
||||||
class="fixed bottom-16 left-1/2 z-50 flex -translate-x-1/2 items-center gap-1 rounded-xl border border-border bg-card/95 px-2 py-1.5 shadow-lg backdrop-blur-sm"
|
|
||||||
>
|
|
||||||
{#each pages as page (page.id)}
|
|
||||||
<div
|
|
||||||
class="group flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm transition-colors
|
|
||||||
{minimizedPagesStore.activePageId === page.id
|
|
||||||
? 'bg-primary/10 text-primary'
|
|
||||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onclick={() => minimizedPagesStore.maximize(page.id)}
|
|
||||||
class="truncate max-w-[120px]"
|
|
||||||
>
|
|
||||||
{page.title}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onclick={() => minimizedPagesStore.remove(page.id)}
|
|
||||||
class="opacity-0 transition-opacity group-hover:opacity-100"
|
|
||||||
>
|
|
||||||
<X size={12} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { LocalLabel } from '../types';
|
|
||||||
import { viewStore } from '../stores/view.svelte';
|
|
||||||
import { X } from '@manacore/shared-icons';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
labels: LocalLabel[];
|
|
||||||
collapsed?: boolean;
|
|
||||||
onToggleCollapse?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { labels, collapsed = false, onToggleCollapse }: Props = $props();
|
|
||||||
|
|
||||||
function handleSelect(id: string) {
|
|
||||||
if (viewStore.currentView === 'label' && viewStore.currentLabelId === id) {
|
|
||||||
viewStore.setInbox();
|
|
||||||
} else {
|
|
||||||
viewStore.setLabel(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if labels.length > 0 && !collapsed}
|
|
||||||
<div class="flex items-center gap-1.5 overflow-x-auto pb-1">
|
|
||||||
{#if viewStore.currentView === 'label'}
|
|
||||||
<button
|
|
||||||
onclick={() => viewStore.setInbox()}
|
|
||||||
class="flex items-center gap-1 rounded-full border border-primary/30 bg-primary/5 px-2 py-0.5 text-xs text-primary"
|
|
||||||
>
|
|
||||||
<X size={10} />
|
|
||||||
Filter
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{#each labels as label (label.id)}
|
|
||||||
<button
|
|
||||||
onclick={() => handleSelect(label.id)}
|
|
||||||
class="flex-shrink-0 rounded-full border px-2.5 py-1 text-xs font-medium transition-colors
|
|
||||||
{viewStore.currentView === 'label' && viewStore.currentLabelId === label.id
|
|
||||||
? 'border-primary bg-primary/10 text-primary'
|
|
||||||
: 'border-border text-muted-foreground hover:border-primary/50'}"
|
|
||||||
>
|
|
||||||
<span class="mr-1 inline-block h-2 w-2 rounded-full" style="background-color: {label.color}"
|
|
||||||
></span>
|
|
||||||
{label.name}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { _ } from 'svelte-i18n';
|
|
||||||
import { viewStore } from '../stores/view.svelte';
|
|
||||||
import type { SortBy } from '../types';
|
|
||||||
import {
|
|
||||||
SortAscending,
|
|
||||||
SortDescending,
|
|
||||||
MagnifyingGlass,
|
|
||||||
FunnelSimple,
|
|
||||||
Columns,
|
|
||||||
List,
|
|
||||||
} from '@manacore/shared-icons';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
showBoardToggle?: boolean;
|
|
||||||
isBoardView?: boolean;
|
|
||||||
onToggleBoard?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { showBoardToggle = false, isBoardView = false, onToggleBoard }: Props = $props();
|
|
||||||
|
|
||||||
let showSortMenu = $state(false);
|
|
||||||
|
|
||||||
const sortOptions: { value: SortBy; label: string }[] = $derived([
|
|
||||||
{ value: 'order', label: $_('todo.sortManual') },
|
|
||||||
{ value: 'dueDate', label: $_('todo.sortDueDate') },
|
|
||||||
{ value: 'priority', label: $_('todo.sortPriority') },
|
|
||||||
{ value: 'title', label: $_('todo.sortName') },
|
|
||||||
{ value: 'createdAt', label: $_('todo.sortCreated') },
|
|
||||||
]);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<!-- Search toggle -->
|
|
||||||
<button
|
|
||||||
onclick={() => {
|
|
||||||
if (viewStore.currentView === 'search') viewStore.setInbox();
|
|
||||||
else viewStore.setSearch('');
|
|
||||||
}}
|
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-lg transition-colors
|
|
||||||
{viewStore.currentView === 'search'
|
|
||||||
? 'bg-primary/10 text-primary'
|
|
||||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
|
|
||||||
title={$_('todo.search')}
|
|
||||||
>
|
|
||||||
<MagnifyingGlass size={16} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Sort -->
|
|
||||||
<div class="relative">
|
|
||||||
<button
|
|
||||||
onclick={() => (showSortMenu = !showSortMenu)}
|
|
||||||
class="flex h-8 items-center gap-1 rounded-lg px-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
||||||
title={$_('todo.sort')}
|
|
||||||
>
|
|
||||||
{#if viewStore.sortOrder === 'asc'}
|
|
||||||
<SortAscending size={16} />
|
|
||||||
{:else}
|
|
||||||
<SortDescending size={16} />
|
|
||||||
{/if}
|
|
||||||
<span class="text-xs"
|
|
||||||
>{sortOptions.find((o) => o.value === viewStore.sortBy)?.label ?? 'Sort'}</span
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if showSortMenu}
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
||||||
<div class="fixed inset-0 z-40" onclick={() => (showSortMenu = false)}></div>
|
|
||||||
<div
|
|
||||||
class="absolute right-0 top-full z-50 mt-1 min-w-[140px] rounded-lg border border-border bg-card p-1 shadow-lg"
|
|
||||||
>
|
|
||||||
{#each sortOptions as opt}
|
|
||||||
<button
|
|
||||||
onclick={() => {
|
|
||||||
if (viewStore.sortBy === opt.value) {
|
|
||||||
viewStore.toggleSortOrder();
|
|
||||||
} else {
|
|
||||||
viewStore.setSort(opt.value);
|
|
||||||
}
|
|
||||||
showSortMenu = false;
|
|
||||||
}}
|
|
||||||
class="flex w-full items-center justify-between rounded-md px-3 py-1.5 text-sm hover:bg-muted
|
|
||||||
{viewStore.sortBy === opt.value ? 'text-primary font-medium' : 'text-foreground'}"
|
|
||||||
>
|
|
||||||
{opt.label}
|
|
||||||
{#if viewStore.sortBy === opt.value}
|
|
||||||
<span class="text-xs text-muted-foreground">
|
|
||||||
{viewStore.sortOrder === 'asc' ? '↑' : '↓'}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Show completed toggle -->
|
|
||||||
<button
|
|
||||||
onclick={() => viewStore.toggleShowCompleted()}
|
|
||||||
class="flex h-8 items-center gap-1 rounded-lg px-2 text-xs transition-colors
|
|
||||||
{viewStore.showCompleted
|
|
||||||
? 'bg-primary/10 text-primary'
|
|
||||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
|
|
||||||
>
|
|
||||||
<FunnelSimple size={14} />
|
|
||||||
{$_('todo.showCompleted')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Board/List toggle -->
|
|
||||||
{#if showBoardToggle}
|
|
||||||
<button
|
|
||||||
onclick={onToggleBoard}
|
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
||||||
title={isBoardView ? $_('todo.listView') : $_('todo.boardView')}
|
|
||||||
>
|
|
||||||
{#if isBoardView}
|
|
||||||
<List size={16} />
|
|
||||||
{:else}
|
|
||||||
<Columns size={16} />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
@ -5,11 +5,9 @@
|
||||||
// Stores
|
// Stores
|
||||||
export { tasksStore } from './stores/tasks.svelte';
|
export { tasksStore } from './stores/tasks.svelte';
|
||||||
export { boardViewsStore } from './stores/board-views.svelte';
|
export { boardViewsStore } from './stores/board-views.svelte';
|
||||||
export { viewStore } from './stores/view.svelte';
|
|
||||||
export { labelsStore } from './stores/labels.svelte';
|
export { labelsStore } from './stores/labels.svelte';
|
||||||
export { remindersStore } from './stores/reminders.svelte';
|
export { remindersStore } from './stores/reminders.svelte';
|
||||||
export { todoSettings } from './stores/settings.svelte';
|
export { todoSettings } from './stores/settings.svelte';
|
||||||
export { minimizedPagesStore } from './stores/minimized-pages.svelte';
|
|
||||||
export { contactsStore } from './stores/contacts.svelte';
|
export { contactsStore } from './stores/contacts.svelte';
|
||||||
|
|
||||||
// Queries
|
// Queries
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/**
|
|
||||||
* Minimized Pages Store — Multi-page system with minimized tabs.
|
|
||||||
*
|
|
||||||
* Allows users to "minimize" views to a tab bar and restore them later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface MinimizedPage {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
icon?: string;
|
|
||||||
route?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pages = $state<MinimizedPage[]>([]);
|
|
||||||
let activePageId = $state<string | null>(null);
|
|
||||||
let showPicker = $state(false);
|
|
||||||
|
|
||||||
export const minimizedPagesStore = {
|
|
||||||
get pages() {
|
|
||||||
return pages;
|
|
||||||
},
|
|
||||||
get activePageId() {
|
|
||||||
return activePageId;
|
|
||||||
},
|
|
||||||
get showPicker() {
|
|
||||||
return showPicker;
|
|
||||||
},
|
|
||||||
|
|
||||||
minimize(page: MinimizedPage) {
|
|
||||||
if (!pages.find((p) => p.id === page.id)) {
|
|
||||||
pages = [...pages, page];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
restore(id: string) {
|
|
||||||
activePageId = id;
|
|
||||||
},
|
|
||||||
|
|
||||||
remove(id: string) {
|
|
||||||
pages = pages.filter((p) => p.id !== id);
|
|
||||||
if (activePageId === id) {
|
|
||||||
activePageId = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
maximize(id: string) {
|
|
||||||
activePageId = id;
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePicker() {
|
|
||||||
showPicker = !showPicker;
|
|
||||||
},
|
|
||||||
|
|
||||||
closePicker() {
|
|
||||||
showPicker = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
pages = [];
|
|
||||||
activePageId = null;
|
|
||||||
showPicker = false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
/**
|
|
||||||
* View Store — Manages current view state using Svelte 5 runes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ViewType, SortBy, SortOrder } from '../types';
|
|
||||||
|
|
||||||
let currentView = $state<ViewType>('inbox');
|
|
||||||
let currentLabelId = $state<string | null>(null);
|
|
||||||
let currentProjectId = $state<string | null>(null);
|
|
||||||
let searchQuery = $state('');
|
|
||||||
let sortBy = $state<SortBy>('order');
|
|
||||||
let sortOrder = $state<SortOrder>('asc');
|
|
||||||
let showCompleted = $state(false);
|
|
||||||
|
|
||||||
export const viewStore = {
|
|
||||||
get currentView() {
|
|
||||||
return currentView;
|
|
||||||
},
|
|
||||||
get currentLabelId() {
|
|
||||||
return currentLabelId;
|
|
||||||
},
|
|
||||||
get currentProjectId() {
|
|
||||||
return currentProjectId;
|
|
||||||
},
|
|
||||||
get searchQuery() {
|
|
||||||
return searchQuery;
|
|
||||||
},
|
|
||||||
get sortBy() {
|
|
||||||
return sortBy;
|
|
||||||
},
|
|
||||||
get sortOrder() {
|
|
||||||
return sortOrder;
|
|
||||||
},
|
|
||||||
get showCompleted() {
|
|
||||||
return showCompleted;
|
|
||||||
},
|
|
||||||
|
|
||||||
setInbox() {
|
|
||||||
currentView = 'inbox';
|
|
||||||
currentLabelId = null;
|
|
||||||
currentProjectId = null;
|
|
||||||
searchQuery = '';
|
|
||||||
},
|
|
||||||
|
|
||||||
setToday() {
|
|
||||||
currentView = 'today';
|
|
||||||
currentLabelId = null;
|
|
||||||
searchQuery = '';
|
|
||||||
},
|
|
||||||
|
|
||||||
setUpcoming() {
|
|
||||||
currentView = 'upcoming';
|
|
||||||
currentLabelId = null;
|
|
||||||
searchQuery = '';
|
|
||||||
},
|
|
||||||
|
|
||||||
setLabel(labelId: string) {
|
|
||||||
currentView = 'label';
|
|
||||||
currentLabelId = labelId;
|
|
||||||
searchQuery = '';
|
|
||||||
},
|
|
||||||
|
|
||||||
setCompleted() {
|
|
||||||
currentView = 'completed';
|
|
||||||
currentLabelId = null;
|
|
||||||
searchQuery = '';
|
|
||||||
},
|
|
||||||
|
|
||||||
setSearch(query: string) {
|
|
||||||
currentView = 'search';
|
|
||||||
currentLabelId = null;
|
|
||||||
searchQuery = query;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSearchQuery(query: string) {
|
|
||||||
searchQuery = query;
|
|
||||||
},
|
|
||||||
|
|
||||||
setSort(by: SortBy, order: SortOrder = 'asc') {
|
|
||||||
sortBy = by;
|
|
||||||
sortOrder = order;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleSortOrder() {
|
|
||||||
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleShowCompleted() {
|
|
||||||
showCompleted = !showCompleted;
|
|
||||||
},
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
currentView = 'inbox';
|
|
||||||
currentLabelId = null;
|
|
||||||
currentProjectId = null;
|
|
||||||
searchQuery = '';
|
|
||||||
sortBy = 'order';
|
|
||||||
sortOrder = 'asc';
|
|
||||||
showCompleted = false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue