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 editTitle = $state(entry.title || '');
|
||||||
let editContent = $state(entry.content);
|
let editContent = $state(entry.content);
|
||||||
let editType = $state(entry.type);
|
let editType = $state(entry.type);
|
||||||
|
let editDueDate = $state(entry.dueDate || '');
|
||||||
|
|
||||||
// Sync edit state when entry changes (after invalidateAll)
|
// Sync edit state when entry changes (after invalidateAll)
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
editTitle = entry.title || '';
|
editTitle = entry.title || '';
|
||||||
editContent = entry.content;
|
editContent = entry.content;
|
||||||
editType = entry.type;
|
editType = entry.type;
|
||||||
|
editDueDate = entry.dueDate || '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,6 +157,34 @@
|
|||||||
// invalidateAll is called by ImageUpload, so nothing extra needed here
|
// 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) {
|
async function handleCameraInput(e: Event) {
|
||||||
const input = e.target as HTMLInputElement;
|
const input = e.target as HTMLInputElement;
|
||||||
const file = input.files?.[0];
|
const file = input.files?.[0];
|
||||||
@@ -266,6 +296,14 @@
|
|||||||
{#if isSaving}
|
{#if isSaving}
|
||||||
<span class="text-xs text-gray-400">Saving...</span>
|
<span class="text-xs text-gray-400">Saving...</span>
|
||||||
{/if}
|
{/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}
|
{#if entry.images?.length > 0 && !expanded}
|
||||||
<span
|
<span
|
||||||
class="flex items-center gap-1 text-xs text-gray-500"
|
class="flex items-center gap-1 text-xs text-gray-500"
|
||||||
@@ -404,6 +442,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<label for="edit-type-{entry.id}" class="block text-sm font-medium text-gray-700 mb-1"
|
<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();
|
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>
|
</script>
|
||||||
|
|
||||||
{#if entries.length === 0}
|
{#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>
|
<p class="text-sm mt-1">Use the capture bar below to add your first entry</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
|
{#if pinnedEntries.length > 0}
|
||||||
{#each entries as entry (entry.id)}
|
<div class="mb-4">
|
||||||
<EntryCard {entry} />
|
<h2 class="text-sm font-medium text-gray-500 uppercase tracking-wide mb-2 px-4 md:px-0">Pinned</h2>
|
||||||
{/each}
|
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
|
||||||
</div>
|
{#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}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user