Skip to content
41 changes: 41 additions & 0 deletions apps/web/src/app/api/v1/task-board-status-templates/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
createAdminClient,
createClient,
} from '@tuturuuu/supabase/next/server';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
try {
const supabase = await createClient(request);
const {
data: { user },
error: authError,
} = await supabase.auth.getUser();

if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

const sbAdmin = await createAdminClient();
const { data: templates, error } = await sbAdmin
.from('task_board_status_templates')
.select('*')
.order('is_default', { ascending: false })
.order('name', { ascending: true });

if (error) {
return NextResponse.json(
{ error: 'Failed to fetch status templates' },
{ status: 500 }
);
}

return NextResponse.json({ templates: templates ?? [] });
} catch (error) {
console.error('Error fetching task board status templates:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export async function POST(
? (draft.priority as ValidPriority)
: undefined;

const newTask = await createTask(supabase, listId, {
const newTask = await createTask(wsId, listId, {
name: draft.name,
description: draft.description || undefined,
priority,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { google } from '@ai-sdk/google';
import { createClient } from '@tuturuuu/supabase/next/server';
import { createClient, createAdminClient } from '@tuturuuu/supabase/next/server';
import { embed } from 'ai';
import { NextResponse } from 'next/server';

Expand Down Expand Up @@ -38,8 +38,10 @@ export async function POST(_: Request, { params }: Params) {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
}

const sbAdmin = await createAdminClient();

// Fetch the task
const { data: task, error: fetchError } = await supabase
const { data: task, error: fetchError } = await sbAdmin
.from('tasks')
.select('id, name, description')
.eq('id', taskId)
Expand Down Expand Up @@ -80,7 +82,7 @@ export async function POST(_: Request, { params }: Params) {
});

// Update task with embedding
const { error: updateError } = await supabase
const { error: updateError } = await sbAdmin
.from('tasks')
.update({ embedding: JSON.stringify(embedding) })
.eq('id', taskId);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/command/add-task-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function AddTaskForm({
const workspaceId = wsId;

// Fetch board config for estimation settings
const { data: boardConfig } = useBoardConfig(selectedBoardId);
const { data: boardConfig } = useBoardConfig(selectedBoardId, wsId);

// Fetch workspace labels (only after board and list are selected)
const { data: workspaceLabels = [], isLoading: labelsLoading } = useQuery({
Expand Down
27 changes: 22 additions & 5 deletions packages/apis/src/tu-do/tasks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ export async function GET(
);
const includeRelationshipSummary =
includeRelationshipSummaryParam !== 'false';
const includeDeletedParam = url.searchParams.get('includeDeleted');
const includeDeletedMode =
includeDeletedParam === 'only'
? 'only'
: includeDeletedParam === 'all'
? 'all'
: 'none';
const includeCount = url.searchParams.get('includeCount') === 'true';

const forTimeTracking = url.searchParams.get('forTimeTracking') === 'true';

Expand Down Expand Up @@ -221,10 +229,16 @@ export async function GET(
status
)
)
`
`,
includeCount ? { count: 'exact' } : undefined
)
.eq('task_lists.workspace_boards.ws_id', normalizedWorkspaceId)
.is('deleted_at', null);
.eq('task_lists.workspace_boards.ws_id', normalizedWorkspaceId);

if (includeDeletedMode === 'none') {
query = query.is('deleted_at', null);
} else if (includeDeletedMode === 'only') {
query = query.not('deleted_at', 'is', null);
}

query = query.eq('task_lists.deleted', false);

Expand All @@ -244,7 +258,7 @@ export async function GET(
query = query.ilike('name', `%${searchQuery}%`);
}

const { data, error } = await query
const { data, error, count } = await query
.order('sort_key', { ascending: true, nullsFirst: false })
.order('created_at', { ascending: false })
.range(offset, offset + limit - 1);
Expand Down Expand Up @@ -316,7 +330,10 @@ export async function GET(
};
});

return NextResponse.json({ tasks: tasksWithRelationshipSummary });
return NextResponse.json({
tasks: tasksWithRelationshipSummary,
...(includeCount ? { count: count ?? 0 } : {}),
});
} catch (error) {
console.error('Error fetching tasks:', error);
return NextResponse.json(
Expand Down
1 change: 1 addition & 0 deletions packages/internal-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export {
getWorkspaceTaskProject,
getWorkspaceTaskProjectTasks,
getWorkspaceTaskRelationships,
listTaskBoardStatusTemplates,
listWorkspaceBoardsWithLists,
listWorkspaceLabels,
listWorkspaceTaskBoards,
Expand Down
38 changes: 38 additions & 0 deletions packages/internal-api/src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
} from '@tuturuuu/types';
import type { TaskPriority } from '@tuturuuu/types/primitives/Priority';
import type { Task } from '@tuturuuu/types/primitives/Task';
import type { TaskBoardStatusTemplate } from '@tuturuuu/types/primitives/TaskBoard';
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
import type {
CreateTaskRelationshipInput,
Expand Down Expand Up @@ -81,10 +82,13 @@ export interface ListWorkspaceTasksOptions {
limit?: number;
offset?: number;
includeRelationshipSummary?: boolean;
includeDeleted?: boolean | 'only';
includeCount?: boolean;
}

export interface WorkspaceTasksResponse {
tasks: WorkspaceTaskApiTask[];
count?: number;
}

export type WorkspaceTaskBoardListItem = Pick<
Expand Down Expand Up @@ -512,12 +516,31 @@ export async function listWorkspaceTasks(
limit: options?.limit,
offset: options?.offset,
includeRelationshipSummary: options?.includeRelationshipSummary,
includeDeleted:
options?.includeDeleted === 'only'
? 'only'
: options?.includeDeleted === true
? 'all'
: undefined,
includeCount: options?.includeCount,
},
cache: 'no-store',
}
);
}

export async function listTaskBoardStatusTemplates(
options?: InternalApiClientOptions
) {
const client = getInternalApiClient(options);
return client.json<{ templates: TaskBoardStatusTemplate[] }>(
'/api/v1/task-board-status-templates',
{
cache: 'no-store',
}
);
}

export async function getWorkspaceTask(
workspaceId: string,
taskId: string,
Expand Down Expand Up @@ -654,6 +677,21 @@ export async function deleteWorkspaceTask(
);
}

export async function triggerWorkspaceTaskEmbedding(
workspaceId: string,
taskId: string,
options?: InternalApiClientOptions
) {
const client = getInternalApiClient(options);
return client.json<{ success?: true; message?: string }>(
`/api/v1/workspaces/${encodePathSegment(workspaceId)}/tasks/${encodePathSegment(taskId)}/embedding`,
{
method: 'POST',
cache: 'no-store',
}
);
}

export async function moveWorkspaceTask(
workspaceId: string,
taskId: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export function QuickTaskDialog({

setIsSubmitting(true);
try {
const newTask = await createTask(supabase, selectedListId, {
const newTask = await createTask(wsId, selectedListId, {
name: taskName.trim(),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
useBoardConfig,
useWorkspaceLabels,
} from '@tuturuuu/utils/task-helper';
import { useParams } from 'next/navigation';
import { useTheme } from 'next-themes';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useTaskActions } from '../../../hooks/use-task-actions';
Expand Down Expand Up @@ -280,6 +281,8 @@ export function TaskMentionChip({
const queryClient = useQueryClient();
const supabase = createClient();
const { resolvedTheme } = useTheme();
const params = useParams();
const routeWsId = params.wsId as string | undefined;
const isDark = resolvedTheme === 'dark';

// Dialog states
Expand Down Expand Up @@ -365,7 +368,7 @@ export function TaskMentionChip({
});

// Get board config - only fetch when menu opens and we have task data
const { data: boardConfig } = useBoardConfig(task?.board_id);
const { data: boardConfig } = useBoardConfig(task?.board_id, routeWsId);

// Fetch workspace labels
const { data: workspaceLabels = [], isLoading: labelsLoading } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function KanbanBoard({
const { createTask } = useTaskDialog();
const { weekStartsOn } = useCalendarPreferences();

const { data: boardConfig } = useBoardConfig(boardId);
const { data: boardConfig } = useBoardConfig(boardId, workspaceId);

// Move list mutation for reordering columns
const moveListMutation = useMutation({
Expand Down Expand Up @@ -234,6 +234,7 @@ export function KanbanBoard({
onDragOver,
onDragEnd,
} = useKanbanDnd({
wsId: workspaceId,
boardId,
columns,
tasks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,6 @@ function useBulkUpdateCustomDueDate(
*/
function useBulkMoveToBoard(
queryClient: QueryClient,
supabase: SupabaseClient,
boardId: string,
broadcast?: BoardBroadcastFn | null
) {
Expand Down Expand Up @@ -473,7 +472,7 @@ function useBulkMoveToBoard(
for (const taskId of taskIds) {
try {
const result = await moveTaskToBoard(
supabase,
boardId,
taskId,
targetListId,
targetBoardId
Expand Down Expand Up @@ -1917,7 +1916,6 @@ export function useBulkOperations(config: BulkOperationsConfig) {
);
const moveToBoardMutation = useBulkMoveToBoard(
queryClient,
supabase,
boardId,
broadcast
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* Handles sort key calculation with automatic retry on gap exhaustion
*/

import type { SupabaseClient } from '@tuturuuu/supabase/next/client';
import type { Task } from '@tuturuuu/types/primitives/Task';
import { toast } from '@tuturuuu/ui/sonner';
import {
Expand All @@ -16,7 +15,6 @@ import {
* Calculate sort key with automatic retry on gap exhaustion or inverted keys
* If SortKeyGapExhaustedError is thrown (including for inverted keys), normalizes the list and retries once
*
* @param supabase - Supabase client for database operations
* @param prevSortKey - Sort key of the previous task (null if inserting at beginning)
* @param nextSortKey - Sort key of the next task (null if inserting at end)
* @param listId - ID of the list where the task is being inserted
Expand All @@ -25,7 +23,7 @@ import {
* @throws Error if calculation fails even after normalization
*/
export async function calculateSortKeyWithRetry(
supabase: SupabaseClient,
wsId: string,
prevSortKey: number | null | undefined,
nextSortKey: number | null | undefined,
listId: string,
Expand All @@ -48,7 +46,7 @@ export async function calculateSortKeyWithRetry(
try {
// Normalize the list sort keys to match visual order
// If visual order is provided, use it; otherwise normalize by database order
await normalizeListSortKeys(supabase, listId, visualOrderTasks);
await normalizeListSortKeys(wsId, listId, visualOrderTasks);
console.log('✅ List sort keys normalized, refetching...');

// After normalization, we can't reuse the old prev/next values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type {
} from '@dnd-kit/core';
import { arrayMove } from '@dnd-kit/sortable';
import { useQueryClient } from '@tanstack/react-query';
import { createClient } from '@tuturuuu/supabase/next/client';
import type { Task } from '@tuturuuu/types/primitives/Task';
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
import { hasDraggableData } from '@tuturuuu/utils/task-helpers';
Expand All @@ -18,6 +17,7 @@ import { useAutoScroll } from './auto-scroll';
import { calculateSortKeyWithRetry as createCalculateSortKeyWithRetry } from './kanban-sort-helpers';

interface UseKanbanDndProps {
wsId: string;
boardId: string | null;
columns: TaskList[];
tasks: Task[];
Expand All @@ -32,6 +32,7 @@ interface UseKanbanDndProps {
}

export function useKanbanDnd({
wsId,
boardId,
columns,
tasks,
Expand Down Expand Up @@ -66,8 +67,6 @@ export function useKanbanDnd({
const isDraggingRef = useRef(false);

const queryClient = useQueryClient();
const supabase = createClient();

// Use the extracted calculateSortKeyWithRetry helper
const calculateSortKeyWithRetry = useCallback(
(
Expand All @@ -77,13 +76,13 @@ export function useKanbanDnd({
visualOrderTasks?: Pick<Task, 'id' | 'sort_key' | 'created_at'>[]
) =>
createCalculateSortKeyWithRetry(
supabase,
wsId,
prevSortKey,
nextSortKey,
listId,
visualOrderTasks
),
[supabase]
[wsId]
);

// Initialize auto-scroll
Expand Down
Loading
Loading