Mega push vol 5, working on messaging now
This commit is contained in:
@@ -100,6 +100,10 @@
|
||||
let cardTagIds = $state<Set<string>>(new Set());
|
||||
let newTagName = $state("");
|
||||
let showTagInput = $state(false);
|
||||
let editingTagId = $state<string | null>(null);
|
||||
let editTagName = $state("");
|
||||
let editTagColor = $state("");
|
||||
let showTagManager = $state(false);
|
||||
|
||||
const TAG_COLORS = [
|
||||
"#00A3E0",
|
||||
@@ -238,6 +242,38 @@
|
||||
showTagInput = false;
|
||||
}
|
||||
|
||||
function startEditTag(tag: OrgTag) {
|
||||
editingTagId = tag.id;
|
||||
editTagName = tag.name;
|
||||
editTagColor = tag.color || TAG_COLORS[0];
|
||||
}
|
||||
|
||||
async function saveEditTag() {
|
||||
if (!editingTagId || !editTagName.trim()) return;
|
||||
const { error } = await supabase
|
||||
.from("tags")
|
||||
.update({ name: editTagName.trim(), color: editTagColor })
|
||||
.eq("id", editingTagId);
|
||||
if (!error) {
|
||||
orgTags = orgTags.map((t) =>
|
||||
t.id === editingTagId
|
||||
? { ...t, name: editTagName.trim(), color: editTagColor }
|
||||
: t,
|
||||
);
|
||||
}
|
||||
editingTagId = null;
|
||||
}
|
||||
|
||||
async function deleteTag(tagId: string) {
|
||||
if (!confirm("Delete this tag from the organization?")) return;
|
||||
const { error } = await supabase.from("tags").delete().eq("id", tagId);
|
||||
if (!error) {
|
||||
orgTags = orgTags.filter((t) => t.id !== tagId);
|
||||
cardTagIds.delete(tagId);
|
||||
cardTagIds = new Set(cardTagIds);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!isMounted) return;
|
||||
if (mode === "create") {
|
||||
@@ -282,7 +318,10 @@
|
||||
.eq("id", columnId)
|
||||
.single();
|
||||
|
||||
const position = (column as any)?.cards?.[0]?.count ?? 0; // join aggregation not typed
|
||||
const cards = (column as Record<string, unknown> | null)?.cards as
|
||||
| { count: number }[]
|
||||
| undefined;
|
||||
const position = cards?.[0]?.count ?? 0;
|
||||
|
||||
const { data: newCard, error } = await supabase
|
||||
.from("kanban_cards")
|
||||
@@ -300,6 +339,26 @@
|
||||
.single();
|
||||
|
||||
if (!error && newCard) {
|
||||
// Persist checklist items added during creation
|
||||
if (checklist.length > 0) {
|
||||
await supabase.from("kanban_checklist_items").insert(
|
||||
checklist.map((item, i) => ({
|
||||
card_id: newCard.id,
|
||||
title: item.title,
|
||||
position: i,
|
||||
completed: false,
|
||||
})),
|
||||
);
|
||||
}
|
||||
// Persist tags assigned during creation
|
||||
if (cardTagIds.size > 0) {
|
||||
await supabase.from("card_tags").insert(
|
||||
[...cardTagIds].map((tagId) => ({
|
||||
card_id: newCard.id,
|
||||
tag_id: tagId,
|
||||
})),
|
||||
);
|
||||
}
|
||||
onCreate?.(newCard as KanbanCard);
|
||||
onClose();
|
||||
}
|
||||
@@ -307,7 +366,25 @@
|
||||
}
|
||||
|
||||
async function handleAddItem() {
|
||||
if (!card || !newItemTitle.trim()) return;
|
||||
if (!newItemTitle.trim()) return;
|
||||
|
||||
if (mode === "create") {
|
||||
// In create mode, add items locally (no card ID yet)
|
||||
checklist = [
|
||||
...checklist,
|
||||
{
|
||||
id: `temp-${Date.now()}`,
|
||||
card_id: "",
|
||||
title: newItemTitle.trim(),
|
||||
completed: false,
|
||||
position: checklist.length,
|
||||
},
|
||||
];
|
||||
newItemTitle = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!card) return;
|
||||
|
||||
const position = checklist.length;
|
||||
const { data, error } = await supabase
|
||||
@@ -429,10 +506,118 @@
|
||||
|
||||
<!-- Tags -->
|
||||
<div>
|
||||
<span
|
||||
class="px-3 font-bold font-body text-body text-white mb-2 block"
|
||||
>Tags</span
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="px-3 font-bold font-body text-body text-white"
|
||||
>Tags</span
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="text-xs text-light/40 hover:text-light transition-colors"
|
||||
onclick={() => (showTagManager = !showTagManager)}
|
||||
>
|
||||
{showTagManager ? "Done" : "Manage"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if showTagManager}
|
||||
<!-- Tag Manager: edit/delete/create tags -->
|
||||
<div class="space-y-2 mb-3 p-3 bg-background rounded-2xl">
|
||||
{#each orgTags as tag}
|
||||
<div class="flex items-center gap-2 group">
|
||||
{#if editingTagId === tag.id}
|
||||
<div class="flex items-center gap-2 flex-1">
|
||||
<input
|
||||
type="color"
|
||||
class="w-6 h-6 rounded cursor-pointer border-0 bg-transparent"
|
||||
bind:value={editTagColor}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="bg-dark border border-primary rounded-lg px-2 py-1 text-sm text-white flex-1 focus:outline-none"
|
||||
bind:value={editTagName}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === "Enter")
|
||||
saveEditTag();
|
||||
if (e.key === "Escape") {
|
||||
editingTagId = null;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="text-primary text-xs font-bold"
|
||||
onclick={saveEditTag}>Save</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="text-light/40 text-xs"
|
||||
onclick={() =>
|
||||
(editingTagId = null)}
|
||||
>Cancel</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<span
|
||||
class="w-3 h-3 rounded-sm shrink-0"
|
||||
style="background-color: {tag.color ||
|
||||
'#00A3E0'}"
|
||||
></span>
|
||||
<span
|
||||
class="text-sm text-light flex-1 truncate"
|
||||
>{tag.name}</span
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="opacity-0 group-hover:opacity-100 p-0.5 text-light/40 hover:text-light transition-all"
|
||||
onclick={() => startEditTag(tag)}
|
||||
aria-label="Edit tag"
|
||||
>
|
||||
<span
|
||||
class="material-symbols-rounded"
|
||||
style="font-size: 16px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 16;"
|
||||
>edit</span
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="opacity-0 group-hover:opacity-100 p-0.5 text-light/40 hover:text-error transition-all"
|
||||
onclick={() => deleteTag(tag.id)}
|
||||
aria-label="Delete tag"
|
||||
>
|
||||
<span
|
||||
class="material-symbols-rounded"
|
||||
style="font-size: 16px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 16;"
|
||||
>delete</span
|
||||
>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<!-- Inline create new tag -->
|
||||
<div
|
||||
class="flex items-center gap-2 pt-1 border-t border-light/10"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="bg-dark border border-light/20 rounded-lg px-2 py-1 text-sm text-white flex-1 focus:outline-none focus:border-primary"
|
||||
placeholder="New tag name..."
|
||||
bind:value={newTagName}
|
||||
onkeydown={(e) =>
|
||||
e.key === "Enter" && createTag()}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="text-primary text-xs font-bold hover:text-primary/80 whitespace-nowrap"
|
||||
onclick={createTag}
|
||||
disabled={!newTagName.trim()}
|
||||
>
|
||||
+ Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Tag toggle chips -->
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
{#each orgTags as tag}
|
||||
<button
|
||||
@@ -450,42 +635,44 @@
|
||||
{tag.name}
|
||||
</button>
|
||||
{/each}
|
||||
{#if showTagInput}
|
||||
<div class="flex gap-1 items-center">
|
||||
<input
|
||||
type="text"
|
||||
class="bg-dark border border-light/20 rounded-lg px-2 py-1 text-sm text-white w-24 focus:outline-none focus:border-primary"
|
||||
placeholder="Tag name"
|
||||
bind:value={newTagName}
|
||||
onkeydown={(e) =>
|
||||
e.key === "Enter" && createTag()}
|
||||
/>
|
||||
{#if !showTagManager}
|
||||
{#if showTagInput}
|
||||
<div class="flex gap-1 items-center">
|
||||
<input
|
||||
type="text"
|
||||
class="bg-dark border border-light/20 rounded-lg px-2 py-1 text-sm text-white w-24 focus:outline-none focus:border-primary"
|
||||
placeholder="Tag name"
|
||||
bind:value={newTagName}
|
||||
onkeydown={(e) =>
|
||||
e.key === "Enter" && createTag()}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="text-primary text-sm font-bold hover:text-primary/80"
|
||||
onclick={createTag}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-light/40 text-sm hover:text-light"
|
||||
onclick={() => {
|
||||
showTagInput = false;
|
||||
newTagName = "";
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
class="text-primary text-sm font-bold hover:text-primary/80"
|
||||
onclick={createTag}
|
||||
class="rounded-lg px-2 py-1 text-sm text-light/50 hover:text-light border border-dashed border-light/20 hover:border-light/40 transition-colors"
|
||||
onclick={() => (showTagInput = true)}
|
||||
>
|
||||
Add
|
||||
+ New tag
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-light/40 text-sm hover:text-light"
|
||||
onclick={() => {
|
||||
showTagInput = false;
|
||||
newTagName = "";
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg px-2 py-1 text-sm text-light/50 hover:text-light border border-dashed border-light/20 hover:border-light/40 transition-colors"
|
||||
onclick={() => (showTagInput = true)}
|
||||
>
|
||||
+ New tag
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user