Mega push vol 7 mvp lesgoooo
This commit is contained in:
176
src/lib/api/budget.ts
Normal file
176
src/lib/api/budget.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
import type { BudgetCategory, BudgetItem } from '$lib/supabase/types';
|
||||
import { createLogger } from '$lib/utils/logger';
|
||||
|
||||
const log = createLogger('api.budget');
|
||||
|
||||
// Helper to cast supabase for tables not yet in generated types
|
||||
function db(supabase: SupabaseClient) {
|
||||
return supabase as any;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Budget Categories
|
||||
// ============================================================
|
||||
|
||||
export async function fetchBudgetCategories(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string
|
||||
): Promise<BudgetCategory[]> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('budget_categories')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('sort_order');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchBudgetCategories failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as BudgetCategory[];
|
||||
}
|
||||
|
||||
export async function createBudgetCategory(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string,
|
||||
name: string,
|
||||
color?: string
|
||||
): Promise<BudgetCategory> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('budget_categories')
|
||||
.insert({
|
||||
department_id: departmentId,
|
||||
name,
|
||||
color: color ?? '#6366f1',
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createBudgetCategory failed', { error, data: { departmentId, name } });
|
||||
throw error;
|
||||
}
|
||||
return data as BudgetCategory;
|
||||
}
|
||||
|
||||
export async function updateBudgetCategory(
|
||||
supabase: SupabaseClient,
|
||||
categoryId: string,
|
||||
params: Partial<Pick<BudgetCategory, 'name' | 'color' | 'sort_order'>>
|
||||
): Promise<BudgetCategory> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('budget_categories')
|
||||
.update(params)
|
||||
.eq('id', categoryId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateBudgetCategory failed', { error, data: { categoryId } });
|
||||
throw error;
|
||||
}
|
||||
return data as BudgetCategory;
|
||||
}
|
||||
|
||||
export async function deleteBudgetCategory(
|
||||
supabase: SupabaseClient,
|
||||
categoryId: string
|
||||
): Promise<void> {
|
||||
const { error } = await db(supabase)
|
||||
.from('budget_categories')
|
||||
.delete()
|
||||
.eq('id', categoryId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteBudgetCategory failed', { error, data: { categoryId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Budget Items
|
||||
// ============================================================
|
||||
|
||||
export async function fetchBudgetItems(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string
|
||||
): Promise<BudgetItem[]> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('budget_items')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('sort_order');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchBudgetItems failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as BudgetItem[];
|
||||
}
|
||||
|
||||
export async function createBudgetItem(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string,
|
||||
params: {
|
||||
description: string;
|
||||
item_type: 'income' | 'expense';
|
||||
planned_amount?: number;
|
||||
actual_amount?: number;
|
||||
category_id?: string | null;
|
||||
notes?: string;
|
||||
}
|
||||
): Promise<BudgetItem> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('budget_items')
|
||||
.insert({
|
||||
department_id: departmentId,
|
||||
description: params.description,
|
||||
item_type: params.item_type,
|
||||
planned_amount: params.planned_amount ?? 0,
|
||||
actual_amount: params.actual_amount ?? 0,
|
||||
category_id: params.category_id ?? null,
|
||||
notes: params.notes ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createBudgetItem failed', { error, data: { departmentId, description: params.description } });
|
||||
throw error;
|
||||
}
|
||||
return data as BudgetItem;
|
||||
}
|
||||
|
||||
export async function updateBudgetItem(
|
||||
supabase: SupabaseClient,
|
||||
itemId: string,
|
||||
params: Partial<Pick<BudgetItem, 'description' | 'item_type' | 'planned_amount' | 'actual_amount' | 'category_id' | 'notes' | 'sort_order'>>
|
||||
): Promise<BudgetItem> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('budget_items')
|
||||
.update({ ...params, updated_at: new Date().toISOString() })
|
||||
.eq('id', itemId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateBudgetItem failed', { error, data: { itemId } });
|
||||
throw error;
|
||||
}
|
||||
return data as BudgetItem;
|
||||
}
|
||||
|
||||
export async function deleteBudgetItem(
|
||||
supabase: SupabaseClient,
|
||||
itemId: string
|
||||
): Promise<void> {
|
||||
const { error } = await db(supabase)
|
||||
.from('budget_items')
|
||||
.delete()
|
||||
.eq('id', itemId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteBudgetItem failed', { error, data: { itemId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
147
src/lib/api/contacts.ts
Normal file
147
src/lib/api/contacts.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
import type { DepartmentContact } from '$lib/supabase/types';
|
||||
import { createLogger } from '$lib/utils/logger';
|
||||
|
||||
const log = createLogger('api.contacts');
|
||||
|
||||
// Helper to cast supabase for tables not yet in generated types
|
||||
function db(supabase: SupabaseClient) {
|
||||
return supabase as any;
|
||||
}
|
||||
|
||||
export const CONTACT_CATEGORIES = [
|
||||
'general',
|
||||
'vendor',
|
||||
'sponsor',
|
||||
'speaker',
|
||||
'venue',
|
||||
'catering',
|
||||
'av_tech',
|
||||
'transport',
|
||||
'security',
|
||||
'media',
|
||||
] as const;
|
||||
|
||||
export type ContactCategory = (typeof CONTACT_CATEGORIES)[number];
|
||||
|
||||
export const CATEGORY_LABELS: Record<string, string> = {
|
||||
general: 'General',
|
||||
vendor: 'Vendor',
|
||||
sponsor: 'Sponsor',
|
||||
speaker: 'Speaker',
|
||||
venue: 'Venue',
|
||||
catering: 'Catering',
|
||||
av_tech: 'AV / Tech',
|
||||
transport: 'Transport',
|
||||
security: 'Security',
|
||||
media: 'Media',
|
||||
};
|
||||
|
||||
export const CATEGORY_ICONS: Record<string, string> = {
|
||||
general: 'person',
|
||||
vendor: 'storefront',
|
||||
sponsor: 'handshake',
|
||||
speaker: 'mic',
|
||||
venue: 'location_on',
|
||||
catering: 'restaurant',
|
||||
av_tech: 'settings_input_hdmi',
|
||||
transport: 'local_shipping',
|
||||
security: 'shield',
|
||||
media: 'videocam',
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// CRUD
|
||||
// ============================================================
|
||||
|
||||
export async function fetchContacts(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string
|
||||
): Promise<DepartmentContact[]> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('department_contacts')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('name');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchContacts failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as DepartmentContact[];
|
||||
}
|
||||
|
||||
export async function createContact(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string,
|
||||
params: {
|
||||
name: string;
|
||||
role?: string;
|
||||
company?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
website?: string;
|
||||
notes?: string;
|
||||
category?: string;
|
||||
color?: string;
|
||||
},
|
||||
userId?: string
|
||||
): Promise<DepartmentContact> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('department_contacts')
|
||||
.insert({
|
||||
department_id: departmentId,
|
||||
name: params.name,
|
||||
role: params.role ?? null,
|
||||
company: params.company ?? null,
|
||||
email: params.email ?? null,
|
||||
phone: params.phone ?? null,
|
||||
website: params.website ?? null,
|
||||
notes: params.notes ?? null,
|
||||
category: params.category ?? 'general',
|
||||
color: params.color ?? '#00A3E0',
|
||||
created_by: userId ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createContact failed', { error, data: { departmentId, name: params.name } });
|
||||
throw error;
|
||||
}
|
||||
return data as DepartmentContact;
|
||||
}
|
||||
|
||||
export async function updateContact(
|
||||
supabase: SupabaseClient,
|
||||
contactId: string,
|
||||
params: Partial<Pick<DepartmentContact, 'name' | 'role' | 'company' | 'email' | 'phone' | 'website' | 'notes' | 'category' | 'color'>>
|
||||
): Promise<DepartmentContact> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('department_contacts')
|
||||
.update({ ...params, updated_at: new Date().toISOString() })
|
||||
.eq('id', contactId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateContact failed', { error, data: { contactId } });
|
||||
throw error;
|
||||
}
|
||||
return data as DepartmentContact;
|
||||
}
|
||||
|
||||
export async function deleteContact(
|
||||
supabase: SupabaseClient,
|
||||
contactId: string
|
||||
): Promise<void> {
|
||||
const { error } = await db(supabase)
|
||||
.from('department_contacts')
|
||||
.delete()
|
||||
.eq('id', contactId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteContact failed', { error, data: { contactId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
354
src/lib/api/department-dashboard.ts
Normal file
354
src/lib/api/department-dashboard.ts
Normal file
@@ -0,0 +1,354 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
import type { Database, DepartmentDashboard, DashboardPanel, DepartmentChecklist, DepartmentChecklistItem, DepartmentNote, ModuleType, LayoutPreset } from '$lib/supabase/types';
|
||||
import { createLogger } from '$lib/utils/logger';
|
||||
|
||||
const log = createLogger('api.department-dashboard');
|
||||
|
||||
// ============================================================
|
||||
// Dashboard
|
||||
// ============================================================
|
||||
|
||||
export interface DashboardWithPanels extends DepartmentDashboard {
|
||||
panels: DashboardPanel[];
|
||||
}
|
||||
|
||||
export async function fetchDashboard(
|
||||
supabase: SupabaseClient<Database>,
|
||||
departmentId: string
|
||||
): Promise<DashboardWithPanels | null> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_dashboards')
|
||||
.select('*, panels:dashboard_panels(*)')
|
||||
.eq('department_id', departmentId)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
if (error.code === 'PGRST116') return null;
|
||||
log.error('fetchDashboard failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
|
||||
const dashboard = data as any;
|
||||
return {
|
||||
...dashboard,
|
||||
panels: (dashboard.panels ?? []).sort((a: DashboardPanel, b: DashboardPanel) => a.position - b.position),
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateDashboardLayout(
|
||||
supabase: SupabaseClient<Database>,
|
||||
dashboardId: string,
|
||||
layout: LayoutPreset
|
||||
): Promise<DepartmentDashboard> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_dashboards')
|
||||
.update({ layout, updated_at: new Date().toISOString() })
|
||||
.eq('id', dashboardId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateDashboardLayout failed', { error, data: { dashboardId, layout } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DepartmentDashboard;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Panels
|
||||
// ============================================================
|
||||
|
||||
export async function addPanel(
|
||||
supabase: SupabaseClient<Database>,
|
||||
dashboardId: string,
|
||||
module: ModuleType,
|
||||
position: number,
|
||||
width: string = 'half'
|
||||
): Promise<DashboardPanel> {
|
||||
const { data, error } = await supabase
|
||||
.from('dashboard_panels')
|
||||
.insert({ dashboard_id: dashboardId, module, position, width })
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('addPanel failed', { error, data: { dashboardId, module } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DashboardPanel;
|
||||
}
|
||||
|
||||
export async function updatePanel(
|
||||
supabase: SupabaseClient<Database>,
|
||||
panelId: string,
|
||||
params: Partial<Pick<DashboardPanel, 'position' | 'width' | 'config'>>
|
||||
): Promise<DashboardPanel> {
|
||||
const { data, error } = await supabase
|
||||
.from('dashboard_panels')
|
||||
.update(params)
|
||||
.eq('id', panelId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updatePanel failed', { error, data: { panelId } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DashboardPanel;
|
||||
}
|
||||
|
||||
export async function removePanel(
|
||||
supabase: SupabaseClient<Database>,
|
||||
panelId: string
|
||||
): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('dashboard_panels')
|
||||
.delete()
|
||||
.eq('id', panelId);
|
||||
|
||||
if (error) {
|
||||
log.error('removePanel failed', { error, data: { panelId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Checklists
|
||||
// ============================================================
|
||||
|
||||
export interface ChecklistWithItems extends DepartmentChecklist {
|
||||
items: DepartmentChecklistItem[];
|
||||
}
|
||||
|
||||
export async function fetchChecklists(
|
||||
supabase: SupabaseClient<Database>,
|
||||
departmentId: string
|
||||
): Promise<ChecklistWithItems[]> {
|
||||
const { data: checklists, error } = await supabase
|
||||
.from('department_checklists')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('sort_order');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchChecklists failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!checklists || checklists.length === 0) return [];
|
||||
|
||||
const checklistIds = checklists.map(c => c.id);
|
||||
const { data: items, error: itemsError } = await supabase
|
||||
.from('department_checklist_items')
|
||||
.select('*')
|
||||
.in('checklist_id', checklistIds)
|
||||
.order('sort_order');
|
||||
|
||||
if (itemsError) {
|
||||
log.error('fetchChecklistItems failed', { error: itemsError });
|
||||
throw itemsError;
|
||||
}
|
||||
|
||||
const itemsByChecklist: Record<string, DepartmentChecklistItem[]> = {};
|
||||
for (const item of (items ?? [])) {
|
||||
if (!itemsByChecklist[item.checklist_id]) itemsByChecklist[item.checklist_id] = [];
|
||||
itemsByChecklist[item.checklist_id].push(item as unknown as DepartmentChecklistItem);
|
||||
}
|
||||
|
||||
return checklists.map(c => ({
|
||||
...(c as unknown as DepartmentChecklist),
|
||||
items: itemsByChecklist[c.id] ?? [],
|
||||
}));
|
||||
}
|
||||
|
||||
export async function createChecklist(
|
||||
supabase: SupabaseClient<Database>,
|
||||
departmentId: string,
|
||||
title: string,
|
||||
userId?: string
|
||||
): Promise<DepartmentChecklist> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_checklists')
|
||||
.insert({ department_id: departmentId, title, created_by: userId ?? null })
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createChecklist failed', { error, data: { departmentId, title } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DepartmentChecklist;
|
||||
}
|
||||
|
||||
export async function deleteChecklist(
|
||||
supabase: SupabaseClient<Database>,
|
||||
checklistId: string
|
||||
): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('department_checklists')
|
||||
.delete()
|
||||
.eq('id', checklistId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteChecklist failed', { error, data: { checklistId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function renameChecklist(
|
||||
supabase: SupabaseClient<Database>,
|
||||
checklistId: string,
|
||||
title: string
|
||||
): Promise<DepartmentChecklist> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_checklists')
|
||||
.update({ title })
|
||||
.eq('id', checklistId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('renameChecklist failed', { error, data: { checklistId, title } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DepartmentChecklist;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Checklist Items
|
||||
// ============================================================
|
||||
|
||||
export async function addChecklistItem(
|
||||
supabase: SupabaseClient<Database>,
|
||||
checklistId: string,
|
||||
content: string,
|
||||
sortOrder: number = 0
|
||||
): Promise<DepartmentChecklistItem> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_checklist_items')
|
||||
.insert({ checklist_id: checklistId, content, sort_order: sortOrder })
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('addChecklistItem failed', { error, data: { checklistId, content } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DepartmentChecklistItem;
|
||||
}
|
||||
|
||||
export async function updateChecklistItem(
|
||||
supabase: SupabaseClient<Database>,
|
||||
itemId: string,
|
||||
params: Partial<Pick<DepartmentChecklistItem, 'content' | 'is_completed' | 'assigned_to' | 'due_date' | 'sort_order'>>
|
||||
): Promise<DepartmentChecklistItem> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_checklist_items')
|
||||
.update({ ...params, updated_at: new Date().toISOString() })
|
||||
.eq('id', itemId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateChecklistItem failed', { error, data: { itemId } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DepartmentChecklistItem;
|
||||
}
|
||||
|
||||
export async function deleteChecklistItem(
|
||||
supabase: SupabaseClient<Database>,
|
||||
itemId: string
|
||||
): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('department_checklist_items')
|
||||
.delete()
|
||||
.eq('id', itemId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteChecklistItem failed', { error, data: { itemId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function toggleChecklistItem(
|
||||
supabase: SupabaseClient<Database>,
|
||||
itemId: string,
|
||||
isCompleted: boolean
|
||||
): Promise<DepartmentChecklistItem> {
|
||||
return updateChecklistItem(supabase, itemId, { is_completed: isCompleted });
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Notes
|
||||
// ============================================================
|
||||
|
||||
export async function fetchNotes(
|
||||
supabase: SupabaseClient<Database>,
|
||||
departmentId: string
|
||||
): Promise<DepartmentNote[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_notes')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('sort_order');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchNotes failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as unknown as DepartmentNote[];
|
||||
}
|
||||
|
||||
export async function createNote(
|
||||
supabase: SupabaseClient<Database>,
|
||||
departmentId: string,
|
||||
title: string,
|
||||
userId?: string
|
||||
): Promise<DepartmentNote> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_notes')
|
||||
.insert({ department_id: departmentId, title, created_by: userId ?? null })
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createNote failed', { error, data: { departmentId, title } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DepartmentNote;
|
||||
}
|
||||
|
||||
export async function updateNote(
|
||||
supabase: SupabaseClient<Database>,
|
||||
noteId: string,
|
||||
params: Partial<Pick<DepartmentNote, 'title' | 'content' | 'sort_order'>>
|
||||
): Promise<DepartmentNote> {
|
||||
const { data, error } = await supabase
|
||||
.from('department_notes')
|
||||
.update({ ...params, updated_at: new Date().toISOString() })
|
||||
.eq('id', noteId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateNote failed', { error, data: { noteId } });
|
||||
throw error;
|
||||
}
|
||||
return data as unknown as DepartmentNote;
|
||||
}
|
||||
|
||||
export async function deleteNote(
|
||||
supabase: SupabaseClient<Database>,
|
||||
noteId: string
|
||||
): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('department_notes')
|
||||
.delete()
|
||||
.eq('id', noteId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteNote failed', { error, data: { noteId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
176
src/lib/api/schedule.ts
Normal file
176
src/lib/api/schedule.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
import type { ScheduleStage, ScheduleBlock } from '$lib/supabase/types';
|
||||
import { createLogger } from '$lib/utils/logger';
|
||||
|
||||
const log = createLogger('api.schedule');
|
||||
|
||||
// Helper to cast supabase for tables not yet in generated types
|
||||
function db(supabase: SupabaseClient) {
|
||||
return supabase as any;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Stages
|
||||
// ============================================================
|
||||
|
||||
export async function fetchStages(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string
|
||||
): Promise<ScheduleStage[]> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('schedule_stages')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('sort_order');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchStages failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as ScheduleStage[];
|
||||
}
|
||||
|
||||
export async function createStage(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string,
|
||||
name: string,
|
||||
color?: string
|
||||
): Promise<ScheduleStage> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('schedule_stages')
|
||||
.insert({ department_id: departmentId, name, color: color ?? '#6366f1' })
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createStage failed', { error, data: { departmentId, name } });
|
||||
throw error;
|
||||
}
|
||||
return data as ScheduleStage;
|
||||
}
|
||||
|
||||
export async function updateStage(
|
||||
supabase: SupabaseClient,
|
||||
stageId: string,
|
||||
params: Partial<Pick<ScheduleStage, 'name' | 'color' | 'sort_order'>>
|
||||
): Promise<ScheduleStage> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('schedule_stages')
|
||||
.update(params)
|
||||
.eq('id', stageId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateStage failed', { error, data: { stageId } });
|
||||
throw error;
|
||||
}
|
||||
return data as ScheduleStage;
|
||||
}
|
||||
|
||||
export async function deleteStage(
|
||||
supabase: SupabaseClient,
|
||||
stageId: string
|
||||
): Promise<void> {
|
||||
const { error } = await db(supabase)
|
||||
.from('schedule_stages')
|
||||
.delete()
|
||||
.eq('id', stageId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteStage failed', { error, data: { stageId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Blocks
|
||||
// ============================================================
|
||||
|
||||
export async function fetchBlocks(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string
|
||||
): Promise<ScheduleBlock[]> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('schedule_blocks')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('start_time');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchBlocks failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as ScheduleBlock[];
|
||||
}
|
||||
|
||||
export async function createBlock(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string,
|
||||
params: {
|
||||
title: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
stage_id?: string | null;
|
||||
description?: string;
|
||||
color?: string;
|
||||
speaker?: string;
|
||||
},
|
||||
userId?: string
|
||||
): Promise<ScheduleBlock> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('schedule_blocks')
|
||||
.insert({
|
||||
department_id: departmentId,
|
||||
title: params.title,
|
||||
start_time: params.start_time,
|
||||
end_time: params.end_time,
|
||||
stage_id: params.stage_id ?? null,
|
||||
description: params.description ?? null,
|
||||
color: params.color ?? '#6366f1',
|
||||
speaker: params.speaker ?? null,
|
||||
created_by: userId ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createBlock failed', { error, data: { departmentId, title: params.title } });
|
||||
throw error;
|
||||
}
|
||||
return data as ScheduleBlock;
|
||||
}
|
||||
|
||||
export async function updateBlock(
|
||||
supabase: SupabaseClient,
|
||||
blockId: string,
|
||||
params: Partial<Pick<ScheduleBlock, 'title' | 'description' | 'start_time' | 'end_time' | 'stage_id' | 'color' | 'speaker' | 'sort_order'>>
|
||||
): Promise<ScheduleBlock> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('schedule_blocks')
|
||||
.update({ ...params, updated_at: new Date().toISOString() })
|
||||
.eq('id', blockId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateBlock failed', { error, data: { blockId } });
|
||||
throw error;
|
||||
}
|
||||
return data as ScheduleBlock;
|
||||
}
|
||||
|
||||
export async function deleteBlock(
|
||||
supabase: SupabaseClient,
|
||||
blockId: string
|
||||
): Promise<void> {
|
||||
const { error } = await db(supabase)
|
||||
.from('schedule_blocks')
|
||||
.delete()
|
||||
.eq('id', blockId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteBlock failed', { error, data: { blockId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
301
src/lib/api/sponsors.ts
Normal file
301
src/lib/api/sponsors.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
import type { SponsorTier, Sponsor, SponsorDeliverable } from '$lib/supabase/types';
|
||||
import { createLogger } from '$lib/utils/logger';
|
||||
|
||||
const log = createLogger('api.sponsors');
|
||||
|
||||
// Helper to cast supabase for tables not yet in generated types
|
||||
function db(supabase: SupabaseClient) {
|
||||
return supabase as any;
|
||||
}
|
||||
|
||||
export const SPONSOR_STATUSES = ['prospect', 'contacted', 'confirmed', 'declined', 'active'] as const;
|
||||
export type SponsorStatus = (typeof SPONSOR_STATUSES)[number];
|
||||
|
||||
export const STATUS_LABELS: Record<string, string> = {
|
||||
prospect: 'Prospect',
|
||||
contacted: 'Contacted',
|
||||
confirmed: 'Confirmed',
|
||||
declined: 'Declined',
|
||||
active: 'Active',
|
||||
};
|
||||
|
||||
export const STATUS_COLORS: Record<string, string> = {
|
||||
prospect: '#94a3b8',
|
||||
contacted: '#F59E0B',
|
||||
confirmed: '#10B981',
|
||||
declined: '#EF4444',
|
||||
active: '#6366f1',
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Sponsor Tiers
|
||||
// ============================================================
|
||||
|
||||
export async function fetchSponsorTiers(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string
|
||||
): Promise<SponsorTier[]> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsor_tiers')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('sort_order');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchSponsorTiers failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as SponsorTier[];
|
||||
}
|
||||
|
||||
export async function createSponsorTier(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string,
|
||||
name: string,
|
||||
amount?: number,
|
||||
color?: string
|
||||
): Promise<SponsorTier> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsor_tiers')
|
||||
.insert({
|
||||
department_id: departmentId,
|
||||
name,
|
||||
amount: amount ?? 0,
|
||||
color: color ?? '#F59E0B',
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createSponsorTier failed', { error, data: { departmentId, name } });
|
||||
throw error;
|
||||
}
|
||||
return data as SponsorTier;
|
||||
}
|
||||
|
||||
export async function updateSponsorTier(
|
||||
supabase: SupabaseClient,
|
||||
tierId: string,
|
||||
params: Partial<Pick<SponsorTier, 'name' | 'amount' | 'color' | 'sort_order'>>
|
||||
): Promise<SponsorTier> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsor_tiers')
|
||||
.update(params)
|
||||
.eq('id', tierId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateSponsorTier failed', { error, data: { tierId } });
|
||||
throw error;
|
||||
}
|
||||
return data as SponsorTier;
|
||||
}
|
||||
|
||||
export async function deleteSponsorTier(
|
||||
supabase: SupabaseClient,
|
||||
tierId: string
|
||||
): Promise<void> {
|
||||
const { error } = await db(supabase)
|
||||
.from('sponsor_tiers')
|
||||
.delete()
|
||||
.eq('id', tierId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteSponsorTier failed', { error, data: { tierId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Sponsors
|
||||
// ============================================================
|
||||
|
||||
export async function fetchSponsors(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string
|
||||
): Promise<Sponsor[]> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsors')
|
||||
.select('*')
|
||||
.eq('department_id', departmentId)
|
||||
.order('name');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchSponsors failed', { error, data: { departmentId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as Sponsor[];
|
||||
}
|
||||
|
||||
export async function createSponsor(
|
||||
supabase: SupabaseClient,
|
||||
departmentId: string,
|
||||
params: {
|
||||
name: string;
|
||||
tier_id?: string | null;
|
||||
contact_name?: string;
|
||||
contact_email?: string;
|
||||
contact_phone?: string;
|
||||
website?: string;
|
||||
logo_url?: string;
|
||||
status?: SponsorStatus;
|
||||
amount?: number;
|
||||
notes?: string;
|
||||
}
|
||||
): Promise<Sponsor> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsors')
|
||||
.insert({
|
||||
department_id: departmentId,
|
||||
name: params.name,
|
||||
tier_id: params.tier_id ?? null,
|
||||
contact_name: params.contact_name ?? null,
|
||||
contact_email: params.contact_email ?? null,
|
||||
contact_phone: params.contact_phone ?? null,
|
||||
website: params.website ?? null,
|
||||
logo_url: params.logo_url ?? null,
|
||||
status: params.status ?? 'prospect',
|
||||
amount: params.amount ?? 0,
|
||||
notes: params.notes ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createSponsor failed', { error, data: { departmentId, name: params.name } });
|
||||
throw error;
|
||||
}
|
||||
return data as Sponsor;
|
||||
}
|
||||
|
||||
export async function updateSponsor(
|
||||
supabase: SupabaseClient,
|
||||
sponsorId: string,
|
||||
params: Partial<Pick<Sponsor, 'name' | 'tier_id' | 'contact_name' | 'contact_email' | 'contact_phone' | 'website' | 'logo_url' | 'status' | 'amount' | 'notes'>>
|
||||
): Promise<Sponsor> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsors')
|
||||
.update({ ...params, updated_at: new Date().toISOString() })
|
||||
.eq('id', sponsorId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateSponsor failed', { error, data: { sponsorId } });
|
||||
throw error;
|
||||
}
|
||||
return data as Sponsor;
|
||||
}
|
||||
|
||||
export async function deleteSponsor(
|
||||
supabase: SupabaseClient,
|
||||
sponsorId: string
|
||||
): Promise<void> {
|
||||
const { error } = await db(supabase)
|
||||
.from('sponsors')
|
||||
.delete()
|
||||
.eq('id', sponsorId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteSponsor failed', { error, data: { sponsorId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Sponsor Deliverables
|
||||
// ============================================================
|
||||
|
||||
export async function fetchDeliverables(
|
||||
supabase: SupabaseClient,
|
||||
sponsorId: string
|
||||
): Promise<SponsorDeliverable[]> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsor_deliverables')
|
||||
.select('*')
|
||||
.eq('sponsor_id', sponsorId)
|
||||
.order('sort_order');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchDeliverables failed', { error, data: { sponsorId } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as SponsorDeliverable[];
|
||||
}
|
||||
|
||||
export async function fetchAllDeliverables(
|
||||
supabase: SupabaseClient,
|
||||
sponsorIds: string[]
|
||||
): Promise<SponsorDeliverable[]> {
|
||||
if (sponsorIds.length === 0) return [];
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsor_deliverables')
|
||||
.select('*')
|
||||
.in('sponsor_id', sponsorIds)
|
||||
.order('sort_order');
|
||||
|
||||
if (error) {
|
||||
log.error('fetchAllDeliverables failed', { error, data: { sponsorIds } });
|
||||
throw error;
|
||||
}
|
||||
return (data ?? []) as SponsorDeliverable[];
|
||||
}
|
||||
|
||||
export async function createDeliverable(
|
||||
supabase: SupabaseClient,
|
||||
sponsorId: string,
|
||||
description: string,
|
||||
dueDate?: string
|
||||
): Promise<SponsorDeliverable> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsor_deliverables')
|
||||
.insert({
|
||||
sponsor_id: sponsorId,
|
||||
description,
|
||||
due_date: dueDate ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('createDeliverable failed', { error, data: { sponsorId, description } });
|
||||
throw error;
|
||||
}
|
||||
return data as SponsorDeliverable;
|
||||
}
|
||||
|
||||
export async function updateDeliverable(
|
||||
supabase: SupabaseClient,
|
||||
deliverableId: string,
|
||||
params: Partial<Pick<SponsorDeliverable, 'description' | 'is_completed' | 'due_date' | 'sort_order'>>
|
||||
): Promise<SponsorDeliverable> {
|
||||
const { data, error } = await db(supabase)
|
||||
.from('sponsor_deliverables')
|
||||
.update({ ...params, updated_at: new Date().toISOString() })
|
||||
.eq('id', deliverableId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
log.error('updateDeliverable failed', { error, data: { deliverableId } });
|
||||
throw error;
|
||||
}
|
||||
return data as SponsorDeliverable;
|
||||
}
|
||||
|
||||
export async function deleteDeliverable(
|
||||
supabase: SupabaseClient,
|
||||
deliverableId: string
|
||||
): Promise<void> {
|
||||
const { error } = await db(supabase)
|
||||
.from('sponsor_deliverables')
|
||||
.delete()
|
||||
.eq('id', deliverableId);
|
||||
|
||||
if (error) {
|
||||
log.error('deleteDeliverable failed', { error, data: { deliverableId } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user