Mega push vol 4

This commit is contained in:
AlacrisDevs
2026-02-06 16:08:40 +02:00
parent b517bb975c
commit d8bbfd9dc3
95 changed files with 8019 additions and 3946 deletions

View File

@@ -1,5 +1,8 @@
import type { SupabaseClient } from '@supabase/supabase-js';
import type { Database, KanbanBoard, KanbanColumn, KanbanCard } from '$lib/supabase/types';
import { createLogger } from '$lib/utils/logger';
const log = createLogger('api.kanban');
export interface ColumnWithCards extends KanbanColumn {
cards: KanbanCard[];
@@ -19,7 +22,11 @@ export async function fetchBoards(
.eq('org_id', orgId)
.order('created_at');
if (error) throw error;
if (error) {
log.error('fetchBoards failed', { error, data: { orgId } });
throw error;
}
log.debug('fetchBoards ok', { data: { count: data?.length ?? 0 } });
return data ?? [];
}
@@ -33,7 +40,10 @@ export async function fetchBoardWithColumns(
.eq('id', boardId)
.single();
if (boardError) throw boardError;
if (boardError) {
log.error('fetchBoardWithColumns failed (board)', { error: boardError, data: { boardId } });
throw boardError;
}
if (!board) return null;
const { data: columns, error: colError } = await supabase
@@ -42,22 +52,55 @@ export async function fetchBoardWithColumns(
.eq('board_id', boardId)
.order('position');
if (colError) throw colError;
if (colError) {
log.error('fetchBoardWithColumns failed (columns)', { error: colError, data: { boardId } });
throw colError;
}
const columnIds = (columns ?? []).map((c) => c.id);
const { data: cards, error: cardError } = await supabase
.from('kanban_cards')
.select('*')
.in('column_id', (columns ?? []).map((c) => c.id))
.in('column_id', columnIds)
.order('position');
if (cardError) throw cardError;
if (cardError) {
log.error('fetchBoardWithColumns failed (cards)', { error: cardError, data: { boardId } });
throw cardError;
}
const cardsByColumn = new Map<string, KanbanCard[]>();
// Fetch tags for all cards in one query
const cardIds = (cards ?? []).map((c) => c.id);
let cardTagsMap = new Map<string, { id: string; name: string; color: string | null }[]>();
if (cardIds.length > 0) {
const { data: cardTags } = await supabase
.from('card_tags')
.select('card_id, tags:tag_id (id, name, color)')
.in('card_id', cardIds);
(cardTags ?? []).forEach((ct: any) => {
const tag = Array.isArray(ct.tags) ? ct.tags[0] : ct.tags;
if (!tag) return;
if (!cardTagsMap.has(ct.card_id)) {
cardTagsMap.set(ct.card_id, []);
}
cardTagsMap.get(ct.card_id)!.push(tag);
});
}
const cardsByColumn = new Map<string, (KanbanCard & { tags?: { id: string; name: string; color: string | null }[] })[]>();
(cards ?? []).forEach((card) => {
if (!cardsByColumn.has(card.column_id)) {
cardsByColumn.set(card.column_id, []);
const colId = card.column_id;
if (!colId) return;
if (!cardsByColumn.has(colId)) {
cardsByColumn.set(colId, []);
}
cardsByColumn.get(card.column_id)!.push(card);
cardsByColumn.get(colId)!.push({
...card,
tags: cardTagsMap.get(card.id) ?? []
});
});
return {
@@ -74,13 +117,17 @@ export async function createBoard(
orgId: string,
name: string
): Promise<KanbanBoard> {
log.info('createBoard', { data: { orgId, name } });
const { data, error } = await supabase
.from('kanban_boards')
.insert({ org_id: orgId, name })
.select()
.single();
if (error) throw error;
if (error) {
log.error('createBoard failed', { error, data: { orgId, name } });
throw error;
}
// Create default columns
const defaultColumns = ['To Do', 'In Progress', 'Done'];
@@ -101,7 +148,10 @@ export async function updateBoard(
name: string
): Promise<void> {
const { error } = await supabase.from('kanban_boards').update({ name }).eq('id', id);
if (error) throw error;
if (error) {
log.error('updateBoard failed', { error, data: { id, name } });
throw error;
}
}
export async function deleteBoard(
@@ -109,7 +159,10 @@ export async function deleteBoard(
id: string
): Promise<void> {
const { error } = await supabase.from('kanban_boards').delete().eq('id', id);
if (error) throw error;
if (error) {
log.error('deleteBoard failed', { error, data: { id } });
throw error;
}
}
export async function createColumn(
@@ -124,7 +177,10 @@ export async function createColumn(
.select()
.single();
if (error) throw error;
if (error) {
log.error('createColumn failed', { error, data: { boardId, name, position } });
throw error;
}
return data;
}
@@ -134,7 +190,10 @@ export async function updateColumn(
updates: Partial<Pick<KanbanColumn, 'name' | 'position' | 'color'>>
): Promise<void> {
const { error } = await supabase.from('kanban_columns').update(updates).eq('id', id);
if (error) throw error;
if (error) {
log.error('updateColumn failed', { error, data: { id, updates } });
throw error;
}
}
export async function deleteColumn(
@@ -142,7 +201,10 @@ export async function deleteColumn(
id: string
): Promise<void> {
const { error } = await supabase.from('kanban_columns').delete().eq('id', id);
if (error) throw error;
if (error) {
log.error('deleteColumn failed', { error, data: { id } });
throw error;
}
}
export async function createCard(
@@ -163,7 +225,10 @@ export async function createCard(
.select()
.single();
if (error) throw error;
if (error) {
log.error('createCard failed', { error, data: { columnId, title, position } });
throw error;
}
return data;
}
@@ -173,7 +238,10 @@ export async function updateCard(
updates: Partial<Pick<KanbanCard, 'title' | 'description' | 'column_id' | 'position' | 'due_date' | 'color'>>
): Promise<void> {
const { error } = await supabase.from('kanban_cards').update(updates).eq('id', id);
if (error) throw error;
if (error) {
log.error('updateCard failed', { error, data: { id, updates } });
throw error;
}
}
export async function deleteCard(
@@ -181,7 +249,10 @@ export async function deleteCard(
id: string
): Promise<void> {
const { error } = await supabase.from('kanban_cards').delete().eq('id', id);
if (error) throw error;
if (error) {
log.error('deleteCard failed', { error, data: { id } });
throw error;
}
}
export async function moveCard(
@@ -190,12 +261,48 @@ export async function moveCard(
newColumnId: string,
newPosition: number
): Promise<void> {
const { error } = await supabase
// Fetch all cards in the target column (ordered by position)
const { data: targetCards, error: fetchErr } = await supabase
.from('kanban_cards')
.update({ column_id: newColumnId, position: newPosition })
.eq('id', cardId);
.select('id, position')
.eq('column_id', newColumnId)
.order('position');
if (error) throw error;
if (fetchErr) {
log.error('moveCard: failed to fetch target column cards', { error: fetchErr });
throw fetchErr;
}
// Remove the moved card from the list if it's already in this column
const otherCards = (targetCards ?? []).filter((c) => c.id !== cardId);
// Insert at the new position and reassign sequential positions
const reordered = [
...otherCards.slice(0, newPosition),
{ id: cardId },
...otherCards.slice(newPosition),
];
// Batch update: move card to column + set position, then update siblings
const updates = reordered.map((c, i) => {
if (c.id === cardId) {
return supabase
.from('kanban_cards')
.update({ column_id: newColumnId, position: i })
.eq('id', c.id);
}
return supabase
.from('kanban_cards')
.update({ position: i })
.eq('id', c.id);
});
const results = await Promise.all(updates);
const failed = results.find((r) => r.error);
if (failed?.error) {
log.error('moveCard failed', { error: failed.error, data: { cardId, newColumnId, newPosition } });
throw failed.error;
}
}
export function subscribeToBoard(