feat(04-02): add pin button, due date picker, and pinned section UI
- Add pin button in expanded view with toggle functionality - Add due date picker in expanded view with date input - Show pin indicator and due date in collapsed view - Separate EntryList into pinned and unpinned sections - Pinned section appears at top with header label
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
let editTitle = $state(entry.title || '');
|
||||
let editContent = $state(entry.content);
|
||||
let editType = $state(entry.type);
|
||||
let editDueDate = $state(entry.dueDate || '');
|
||||
|
||||
// Sync edit state when entry changes (after invalidateAll)
|
||||
$effect(() => {
|
||||
@@ -34,6 +35,7 @@
|
||||
editTitle = entry.title || '';
|
||||
editContent = entry.content;
|
||||
editType = entry.type;
|
||||
editDueDate = entry.dueDate || '';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -155,6 +157,34 @@
|
||||
// invalidateAll is called by ImageUpload, so nothing extra needed here
|
||||
}
|
||||
|
||||
async function handleTogglePin() {
|
||||
const formData = new FormData();
|
||||
formData.append('id', entry.id);
|
||||
|
||||
await fetch('?/togglePin', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
await invalidateAll();
|
||||
}
|
||||
|
||||
async function handleDueDateChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
editDueDate = input.value;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('id', entry.id);
|
||||
formData.append('dueDate', input.value);
|
||||
|
||||
await fetch('?/updateDueDate', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
await invalidateAll();
|
||||
}
|
||||
|
||||
async function handleCameraInput(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
@@ -266,6 +296,14 @@
|
||||
{#if isSaving}
|
||||
<span class="text-xs text-gray-400">Saving...</span>
|
||||
{/if}
|
||||
{#if entry.pinned}
|
||||
<svg class="w-4 h-4 text-yellow-500" fill="currentColor" viewBox="0 0 20 20" aria-label="Pinned">
|
||||
<path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v3.586l1.707 1.707A1 1 0 0117 10v1a1 1 0 01-1 1h-4v5a1 1 0 11-2 0v-5H6a1 1 0 01-1-1v-1a1 1 0 01.293-.707L7 7.586V4z" />
|
||||
</svg>
|
||||
{/if}
|
||||
{#if entry.dueDate && !expanded}
|
||||
<span class="text-xs text-gray-500">{entry.dueDate}</span>
|
||||
{/if}
|
||||
{#if entry.images?.length > 0 && !expanded}
|
||||
<span
|
||||
class="flex items-center gap-1 text-xs text-gray-500"
|
||||
@@ -404,6 +442,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pin and Due Date row -->
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleTogglePin}
|
||||
class="p-2 rounded-lg hover:bg-gray-100 {entry.pinned ? 'text-yellow-500' : 'text-gray-400'}"
|
||||
aria-label={entry.pinned ? 'Unpin entry' : 'Pin entry'}
|
||||
>
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v3.586l1.707 1.707A1 1 0 0117 10v1a1 1 0 01-1 1h-4v5a1 1 0 11-2 0v-5H6a1 1 0 01-1-1v-1a1 1 0 01.293-.707L7 7.586V4z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<label for="due-date-{entry.id}" class="block text-sm font-medium text-gray-700 mb-1">Due Date</label>
|
||||
<input
|
||||
id="due-date-{entry.id}"
|
||||
type="date"
|
||||
value={editDueDate}
|
||||
onchange={handleDueDateChange}
|
||||
class="px-3 py-2 border border-gray-200 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label for="edit-type-{entry.id}" class="block text-sm font-medium text-gray-700 mb-1"
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
}
|
||||
|
||||
let { entries }: Props = $props();
|
||||
|
||||
// Separate entries into pinned and unpinned
|
||||
let pinnedEntries = $derived(entries.filter(e => e.pinned));
|
||||
let unpinnedEntries = $derived(entries.filter(e => !e.pinned));
|
||||
</script>
|
||||
|
||||
{#if entries.length === 0}
|
||||
@@ -19,9 +23,22 @@
|
||||
<p class="text-sm mt-1">Use the capture bar below to add your first entry</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
|
||||
{#each entries as entry (entry.id)}
|
||||
<EntryCard {entry} />
|
||||
{/each}
|
||||
</div>
|
||||
{#if pinnedEntries.length > 0}
|
||||
<div class="mb-4">
|
||||
<h2 class="text-sm font-medium text-gray-500 uppercase tracking-wide mb-2 px-4 md:px-0">Pinned</h2>
|
||||
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
|
||||
{#each pinnedEntries as entry (entry.id)}
|
||||
<EntryCard {entry} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if unpinnedEntries.length > 0}
|
||||
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
|
||||
{#each unpinnedEntries as entry (entry.id)}
|
||||
<EntryCard {entry} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user