Asked claude opus to make an audit of code and use coding guidelines etc. This is the fix
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { getTasks, addTask, updateTask, deleteTask } from './api.js';
|
||||
import { formatRelativeDate, isOverdue } from './utils.js';
|
||||
import UserManager from './UserManager.svelte';
|
||||
|
||||
let selectedUserId = $state(null);
|
||||
@@ -97,7 +98,6 @@
|
||||
async function handleAddTask(e) {
|
||||
e.preventDefault();
|
||||
if (!newTitle.trim()) return;
|
||||
// For non-recurrent tasks, require a date
|
||||
if (newRecurrence === 0 && !newDueDay) return;
|
||||
|
||||
const dueTimestamp = newRecurrence > 0
|
||||
@@ -183,59 +183,48 @@
|
||||
newDueDay = `${year}-${month}-${dayNum}`;
|
||||
}
|
||||
|
||||
function formatRelativeDate(timestamp) {
|
||||
const now = Date.now() / 1000;
|
||||
const diff = timestamp - now;
|
||||
const absDiff = Math.abs(diff);
|
||||
|
||||
if (absDiff < 3600) {
|
||||
const mins = Math.round(absDiff / 60);
|
||||
return diff < 0 ? `${mins}m ago` : `in ${mins}m`;
|
||||
}
|
||||
if (absDiff < 86400) {
|
||||
const hours = Math.round(absDiff / 3600);
|
||||
return diff < 0 ? `${hours}h ago` : `in ${hours}h`;
|
||||
}
|
||||
const days = Math.round(absDiff / 86400);
|
||||
return diff < 0 ? `${days}d ago` : `in ${days}d`;
|
||||
}
|
||||
|
||||
function isOverdue(task) {
|
||||
if (task.recurrence > 0) return false; // Recurrent tasks don't have overdue
|
||||
return task.due_date < Date.now() / 1000;
|
||||
function taskIsOverdue(task) {
|
||||
if (task.recurrence > 0) return false;
|
||||
return isOverdue(task.due_date);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="task-manager">
|
||||
<div class="w-full">
|
||||
<UserManager bind:selectedUserId onUsersChanged={fetchTasks} />
|
||||
|
||||
{#if selectedUserId}
|
||||
<div class="task-header">
|
||||
<h2 class="task-title">Tasks</h2>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-[0.05em] text-text-primary">Tasks</h2>
|
||||
{#if !showAddForm}
|
||||
<button class="add-task-btn" onclick={() => { showAddForm = true; setTodayForNewTask(); }}>+ Add Task</button>
|
||||
<button
|
||||
class="px-3.5 py-1.5 rounded-lg border border-accent bg-accent/10 text-accent cursor-pointer text-xs font-semibold transition-all hover:bg-accent/20"
|
||||
onclick={() => { showAddForm = true; setTodayForNewTask(); }}
|
||||
>+ Add Task</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if showAddForm}
|
||||
<form class="add-task-form" onsubmit={handleAddTask}>
|
||||
<form class="flex flex-col gap-2 p-3 rounded-[10px] border border-border bg-bg-card mb-3" onsubmit={handleAddTask}>
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
bind:value={newTitle}
|
||||
placeholder="Task title..."
|
||||
class="task-input"
|
||||
class="px-3 py-2 rounded-lg border border-border bg-bg-primary text-text-primary text-[13px] outline-none w-full box-border focus:border-accent"
|
||||
autofocus
|
||||
/>
|
||||
|
||||
<div class="field-row">
|
||||
<div class="field-group">
|
||||
<span class="field-label">Period</span>
|
||||
<div class="period-selector">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col gap-1 flex-1">
|
||||
<span class="text-[10px] font-semibold uppercase tracking-[0.05em] text-text-secondary">Period</span>
|
||||
<div class="flex gap-1">
|
||||
{#each PERIODS as p, i}
|
||||
<button
|
||||
type="button"
|
||||
class="period-btn {hasBit(newPeriod, i) ? 'active' : ''}"
|
||||
class="flex-1 px-2 py-1.5 rounded-md border cursor-pointer text-[11px] transition-all whitespace-nowrap
|
||||
{hasBit(newPeriod, i)
|
||||
? 'border-accent bg-accent/15 text-accent font-semibold'
|
||||
: 'border-border bg-bg-primary text-text-secondary hover:bg-bg-card-hover'}"
|
||||
onclick={() => newPeriod = toggleBit(newPeriod, i)}
|
||||
>{PERIOD_ICONS[i]} {p}</button>
|
||||
{/each}
|
||||
@@ -243,14 +232,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<div class="field-group">
|
||||
<span class="field-label">Recurrence</span>
|
||||
<div class="recurrence-selector">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col gap-1 flex-1">
|
||||
<span class="text-[10px] font-semibold uppercase tracking-[0.05em] text-text-secondary">Recurrence</span>
|
||||
<div class="flex gap-[3px] flex-wrap">
|
||||
{#each DAYS as day, i}
|
||||
<button
|
||||
type="button"
|
||||
class="rec-btn {hasBit(newRecurrence, i) ? 'active' : ''}"
|
||||
class="px-2 py-[5px] rounded-md border cursor-pointer text-[11px] transition-all min-w-8 text-center
|
||||
{hasBit(newRecurrence, i)
|
||||
? 'border-accent bg-accent/15 text-accent font-semibold'
|
||||
: 'border-border bg-bg-primary text-text-secondary hover:bg-bg-card-hover'}"
|
||||
onclick={() => newRecurrence = toggleBit(newRecurrence, i)}
|
||||
>{day}</button>
|
||||
{/each}
|
||||
@@ -259,36 +251,46 @@
|
||||
</div>
|
||||
|
||||
{#if newRecurrence === 0}
|
||||
<div class="field-row">
|
||||
<label class="field-group">
|
||||
<span class="field-label">Date</span>
|
||||
<input type="date" bind:value={newDueDay} class="task-date-input" />
|
||||
<div class="flex gap-2">
|
||||
<label class="flex flex-col gap-1 flex-1">
|
||||
<span class="text-[10px] font-semibold uppercase tracking-[0.05em] text-text-secondary">Date</span>
|
||||
<input type="date" bind:value={newDueDay}
|
||||
class="date-input px-3 py-2 rounded-lg border border-border bg-bg-primary text-text-primary text-[13px] outline-none [color-scheme:dark] flex-1 cursor-pointer relative focus:border-accent" />
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn-primary" disabled={!newTitle.trim() || (newRecurrence === 0 && !newDueDay)}>Add</button>
|
||||
<button type="button" class="btn-secondary" onclick={() => { showAddForm = false; newTitle = ''; newDueDay = ''; newRecurrence = 0; newPeriod = 0x01; }}>Cancel</button>
|
||||
<div class="flex gap-1.5">
|
||||
<button type="submit"
|
||||
class="px-4 py-1.5 rounded-lg border-none bg-accent text-white cursor-pointer text-xs font-semibold transition-[filter] hover:brightness-110 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
disabled={!newTitle.trim() || (newRecurrence === 0 && !newDueDay)}>Add</button>
|
||||
<button type="button"
|
||||
class="px-4 py-1.5 rounded-lg border border-border bg-transparent text-text-secondary cursor-pointer text-xs transition-all hover:bg-bg-card-hover"
|
||||
onclick={() => { showAddForm = false; newTitle = ''; newDueDay = ''; newRecurrence = 0; newPeriod = 0x01; }}>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<div class="task-list">
|
||||
<div class="flex flex-col gap-1">
|
||||
{#each tasks as task}
|
||||
<div class="task-item {task.completed ? 'completed' : ''} {isOverdue(task) && !task.completed ? 'overdue' : ''}">
|
||||
<div class="flex items-center justify-between flex-wrap px-3.5 py-2.5 rounded-[10px] border bg-bg-card transition-all hover:bg-bg-card-hover
|
||||
{task.completed ? 'opacity-50' : ''}
|
||||
{taskIsOverdue(task) && !task.completed ? 'border-danger/40' : 'border-border'}">
|
||||
{#if editingTaskId === task.id}
|
||||
<form class="edit-form" onsubmit={saveEdit}>
|
||||
<input type="text" bind:value={editTitle} class="task-input" />
|
||||
<form class="w-full flex flex-col gap-2 p-0" onsubmit={saveEdit}>
|
||||
<input type="text" bind:value={editTitle}
|
||||
class="px-3 py-2 rounded-lg border border-border bg-bg-primary text-text-primary text-[13px] outline-none w-full box-border focus:border-accent" />
|
||||
|
||||
<div class="field-row">
|
||||
<div class="field-group">
|
||||
<span class="field-label">Period</span>
|
||||
<div class="period-selector">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col gap-1 flex-1">
|
||||
<span class="text-[10px] font-semibold uppercase tracking-[0.05em] text-text-secondary">Period</span>
|
||||
<div class="flex gap-1">
|
||||
{#each PERIODS as p, i}
|
||||
<button
|
||||
type="button"
|
||||
class="period-btn {hasBit(editPeriod, i) ? 'active' : ''}"
|
||||
<button type="button"
|
||||
class="flex-1 px-2 py-1.5 rounded-md border cursor-pointer text-[11px] transition-all whitespace-nowrap
|
||||
{hasBit(editPeriod, i)
|
||||
? 'border-accent bg-accent/15 text-accent font-semibold'
|
||||
: 'border-border bg-bg-primary text-text-secondary hover:bg-bg-card-hover'}"
|
||||
onclick={() => editPeriod = toggleBit(editPeriod, i)}
|
||||
>{PERIOD_ICONS[i]} {p}</button>
|
||||
{/each}
|
||||
@@ -296,14 +298,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<div class="field-group">
|
||||
<span class="field-label">Recurrence</span>
|
||||
<div class="recurrence-selector">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col gap-1 flex-1">
|
||||
<span class="text-[10px] font-semibold uppercase tracking-[0.05em] text-text-secondary">Recurrence</span>
|
||||
<div class="flex gap-[3px] flex-wrap">
|
||||
{#each DAYS as day, i}
|
||||
<button
|
||||
type="button"
|
||||
class="rec-btn {hasBit(editRecurrence, i) ? 'active' : ''}"
|
||||
<button type="button"
|
||||
class="px-2 py-[5px] rounded-md border cursor-pointer text-[11px] transition-all min-w-8 text-center
|
||||
{hasBit(editRecurrence, i)
|
||||
? 'border-accent bg-accent/15 text-accent font-semibold'
|
||||
: 'border-border bg-bg-primary text-text-secondary hover:bg-bg-card-hover'}"
|
||||
onclick={() => editRecurrence = toggleBit(editRecurrence, i)}
|
||||
>{day}</button>
|
||||
{/each}
|
||||
@@ -312,425 +316,71 @@
|
||||
</div>
|
||||
|
||||
{#if editRecurrence === 0}
|
||||
<div class="field-row">
|
||||
<label class="field-group">
|
||||
<span class="field-label">Date</span>
|
||||
<input type="date" bind:value={editDueDay} class="task-date-input" />
|
||||
<div class="flex gap-2">
|
||||
<label class="flex flex-col gap-1 flex-1">
|
||||
<span class="text-[10px] font-semibold uppercase tracking-[0.05em] text-text-secondary">Date</span>
|
||||
<input type="date" bind:value={editDueDay}
|
||||
class="date-input px-3 py-2 rounded-lg border border-border bg-bg-primary text-text-primary text-[13px] outline-none [color-scheme:dark] flex-1 cursor-pointer relative focus:border-accent" />
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn-primary btn-sm">Save</button>
|
||||
<button type="button" class="btn-secondary btn-sm" onclick={() => editingTaskId = null}>Cancel</button>
|
||||
<div class="flex gap-1.5">
|
||||
<button type="submit"
|
||||
class="px-2.5 py-1 rounded-lg border-none bg-accent text-white cursor-pointer text-[11px] font-semibold transition-[filter] hover:brightness-110">Save</button>
|
||||
<button type="button"
|
||||
class="px-2.5 py-1 rounded-lg border border-border bg-transparent text-text-secondary cursor-pointer text-[11px] transition-all hover:bg-bg-card-hover"
|
||||
onclick={() => editingTaskId = null}>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<div class="task-left">
|
||||
<div class="flex items-center gap-2.5 flex-1 min-w-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={task.completed}
|
||||
onchange={() => handleToggleComplete(task)}
|
||||
class="task-checkbox"
|
||||
class="w-4 h-4 cursor-pointer flex-shrink-0"
|
||||
style="accent-color: var(--color-accent)"
|
||||
/>
|
||||
<div class="task-info">
|
||||
<span class="task-text">{task.title}</span>
|
||||
<div class="task-meta">
|
||||
<span class="task-period">{periodIcons(task.period)} {formatPeriod(task.period)}</span>
|
||||
<div class="flex flex-col gap-0.5 min-w-0">
|
||||
<span class="text-[13px] text-text-primary whitespace-nowrap overflow-hidden text-ellipsis
|
||||
{task.completed ? 'line-through text-text-secondary' : ''}">{task.title}</span>
|
||||
<div class="flex gap-2 items-center flex-wrap">
|
||||
<span class="text-[10px] text-text-secondary font-medium">{periodIcons(task.period)} {formatPeriod(task.period)}</span>
|
||||
{#if task.recurrence > 0}
|
||||
<span class="task-recurrence">🔁 {formatRecurrence(task.recurrence)}</span>
|
||||
<span class="text-[10px] text-accent font-medium">🔁 {formatRecurrence(task.recurrence)}</span>
|
||||
{:else}
|
||||
<span class="task-due {isOverdue(task) && !task.completed ? 'text-overdue' : ''}">
|
||||
<span class="text-[10px] font-medium {taskIsOverdue(task) && !task.completed ? 'text-danger' : 'text-text-secondary'}">
|
||||
{formatRelativeDate(task.due_date)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-actions">
|
||||
<button class="action-btn" onclick={() => startEditing(task)} title="Edit">✏️</button>
|
||||
<div class="flex gap-1 items-center flex-shrink-0">
|
||||
<button class="bg-transparent border-none cursor-pointer text-xs p-1 rounded opacity-60 transition-[background] hover:opacity-100 hover:bg-bg-card-hover"
|
||||
onclick={() => startEditing(task)} title="Edit">✏️</button>
|
||||
{#if confirmDeleteId === task.id}
|
||||
<button class="action-btn text-danger" onclick={() => handleDelete(task.id)} title="Confirm">✓</button>
|
||||
<button class="action-btn" onclick={() => confirmDeleteId = null} title="Cancel">✕</button>
|
||||
<button class="bg-transparent border-none cursor-pointer text-xs p-1 rounded text-danger opacity-60 hover:opacity-100"
|
||||
onclick={() => handleDelete(task.id)} title="Confirm">✓</button>
|
||||
<button class="bg-transparent border-none cursor-pointer text-xs p-1 rounded opacity-60 hover:opacity-100 hover:bg-bg-card-hover"
|
||||
onclick={() => confirmDeleteId = null} title="Cancel">✕</button>
|
||||
{:else}
|
||||
<button class="action-btn" onclick={() => confirmDeleteId = task.id} title="Delete">🗑️</button>
|
||||
<button class="bg-transparent border-none cursor-pointer text-xs p-1 rounded opacity-60 transition-[background] hover:opacity-100 hover:bg-bg-card-hover"
|
||||
onclick={() => confirmDeleteId = task.id} title="Delete">🗑️</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="empty-state">No tasks yet. Add one above!</div>
|
||||
<div class="text-center p-8 text-text-secondary text-[13px]">No tasks yet. Add one above!</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="empty-state">Select or add a user to see their tasks.</div>
|
||||
<div class="text-center p-8 text-text-secondary text-[13px]">Select or add a user to see their tasks.</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<div class="task-error">{error}</div>
|
||||
<div class="mt-2 px-2.5 py-1.5 rounded-md bg-danger/10 border border-danger/20 text-danger text-[11px]">{error}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.task-manager {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.task-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.add-task-btn {
|
||||
padding: 6px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-accent);
|
||||
background: color-mix(in srgb, var(--color-accent) 10%, transparent);
|
||||
color: var(--color-accent);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.add-task-btn:hover {
|
||||
background: color-mix(in srgb, var(--color-accent) 20%, transparent);
|
||||
}
|
||||
|
||||
.add-task-form, .edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-card);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.task-input {
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.task-input:focus {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.period-selector {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.period-btn {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-primary);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.period-btn:hover {
|
||||
background: var(--color-bg-card-hover);
|
||||
}
|
||||
|
||||
.period-btn.active {
|
||||
border-color: var(--color-accent);
|
||||
background: color-mix(in srgb, var(--color-accent) 15%, transparent);
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.recurrence-selector {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.rec-btn {
|
||||
padding: 5px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-primary);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
transition: all 0.15s;
|
||||
min-width: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rec-btn:hover {
|
||||
background: var(--color-bg-card-hover);
|
||||
}
|
||||
|
||||
.rec-btn.active {
|
||||
border-color: var(--color-accent);
|
||||
background: color-mix(in srgb, var(--color-accent) 15%, transparent);
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.task-date-input {
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
color-scheme: dark;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Make the native picker icon cover the entire input (Chrome/Edge only) */
|
||||
.task-date-input::-webkit-calendar-picker-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.task-date-input:focus {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 6px 16px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 6px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: transparent;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--color-bg-card-hover);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 4px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-card);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.task-item .edit-form {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.task-item:hover {
|
||||
background: var(--color-bg-card-hover);
|
||||
}
|
||||
|
||||
.task-item.completed {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.task-item.overdue {
|
||||
border-color: color-mix(in srgb, var(--color-danger) 40%, transparent);
|
||||
}
|
||||
|
||||
.task-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.task-checkbox {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--color-accent);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.task-text {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.completed .task-text {
|
||||
text-decoration: line-through;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.task-meta {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.task-period {
|
||||
font-size: 10px;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-recurrence {
|
||||
font-size: 10px;
|
||||
color: var(--color-accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-due {
|
||||
font-size: 10px;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text-overdue {
|
||||
color: var(--color-danger) !important;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: background 0.15s;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
opacity: 1;
|
||||
background: var(--color-bg-card-hover);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.task-error {
|
||||
margin-top: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--color-danger) 20%, transparent);
|
||||
color: var(--color-danger);
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user