Mega push vol 5, working on messaging now

This commit is contained in:
AlacrisDevs
2026-02-07 01:31:55 +02:00
parent d8bbfd9dc3
commit e55881b38b
77 changed files with 8478 additions and 1554 deletions

View File

@@ -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>