parent
1534e1f0af
commit
9af0ef5307
17 changed files with 1053 additions and 186 deletions
@ -0,0 +1,14 @@ |
|||||||
|
<script lang="ts"> |
||||||
|
import { toasts } from '$lib/stores/toast'; |
||||||
|
import Toast from './Toast.svelte'; |
||||||
|
</script> |
||||||
|
|
||||||
|
<div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2 max-w-sm"> |
||||||
|
{#each $toasts as toast (toast.id)} |
||||||
|
<Toast |
||||||
|
variant={toast.variant} |
||||||
|
message={toast.message} |
||||||
|
onClose={() => toasts.remove(toast.id)} |
||||||
|
/> |
||||||
|
{/each} |
||||||
|
</div> |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
import { writable } from 'svelte/store'; |
||||||
|
|
||||||
|
export type ToastVariant = 'success' | 'error' | 'warning' | 'info'; |
||||||
|
|
||||||
|
export interface Toast { |
||||||
|
id: string; |
||||||
|
message: string; |
||||||
|
variant: ToastVariant; |
||||||
|
duration?: number; |
||||||
|
} |
||||||
|
|
||||||
|
function createToastStore() { |
||||||
|
const { subscribe, update } = writable<Toast[]>([]); |
||||||
|
|
||||||
|
function add(message: string, variant: ToastVariant = 'info', duration = 5000) { |
||||||
|
const id = crypto.randomUUID(); |
||||||
|
const toast: Toast = { id, message, variant, duration }; |
||||||
|
|
||||||
|
update((toasts) => [...toasts, toast]); |
||||||
|
|
||||||
|
if (duration > 0) { |
||||||
|
setTimeout(() => remove(id), duration); |
||||||
|
} |
||||||
|
|
||||||
|
return id; |
||||||
|
} |
||||||
|
|
||||||
|
function remove(id: string) { |
||||||
|
update((toasts) => toasts.filter((t) => t.id !== id)); |
||||||
|
} |
||||||
|
|
||||||
|
function clear() { |
||||||
|
update(() => []); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
subscribe, |
||||||
|
add, |
||||||
|
remove, |
||||||
|
clear, |
||||||
|
success: (message: string, duration?: number) => add(message, 'success', duration), |
||||||
|
error: (message: string, duration?: number) => add(message, 'error', duration), |
||||||
|
warning: (message: string, duration?: number) => add(message, 'warning', duration), |
||||||
|
info: (message: string, duration?: number) => add(message, 'info', duration) |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export const toasts = createToastStore(); |
||||||
@ -0,0 +1,69 @@ |
|||||||
|
-- Tags system for kanban tasks |
||||||
|
-- Allows categorizing tasks by team, type, priority, etc. |
||||||
|
|
||||||
|
-- Create tags table |
||||||
|
CREATE TABLE IF NOT EXISTS tags ( |
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
||||||
|
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, |
||||||
|
name TEXT NOT NULL, |
||||||
|
color TEXT DEFAULT '#00A3E0', |
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(), |
||||||
|
UNIQUE(org_id, name) |
||||||
|
); |
||||||
|
|
||||||
|
-- Create junction table for card tags (many-to-many) |
||||||
|
CREATE TABLE IF NOT EXISTS card_tags ( |
||||||
|
card_id UUID NOT NULL REFERENCES kanban_cards(id) ON DELETE CASCADE, |
||||||
|
tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE, |
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(), |
||||||
|
PRIMARY KEY (card_id, tag_id) |
||||||
|
); |
||||||
|
|
||||||
|
-- Create indexes |
||||||
|
CREATE INDEX IF NOT EXISTS idx_tags_org ON tags(org_id); |
||||||
|
CREATE INDEX IF NOT EXISTS idx_card_tags_card ON card_tags(card_id); |
||||||
|
CREATE INDEX IF NOT EXISTS idx_card_tags_tag ON card_tags(tag_id); |
||||||
|
|
||||||
|
-- Enable RLS |
||||||
|
ALTER TABLE tags ENABLE ROW LEVEL SECURITY; |
||||||
|
ALTER TABLE card_tags ENABLE ROW LEVEL SECURITY; |
||||||
|
|
||||||
|
-- RLS policies for tags |
||||||
|
CREATE POLICY "Users can view tags in their orgs" |
||||||
|
ON tags FOR SELECT |
||||||
|
USING ( |
||||||
|
org_id IN ( |
||||||
|
SELECT org_id FROM org_members WHERE user_id = auth.uid() |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
CREATE POLICY "Admins can manage tags" |
||||||
|
ON tags FOR ALL |
||||||
|
USING ( |
||||||
|
org_id IN ( |
||||||
|
SELECT org_id FROM org_members |
||||||
|
WHERE user_id = auth.uid() |
||||||
|
AND role IN ('owner', 'admin') |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
-- RLS policies for card_tags |
||||||
|
CREATE POLICY "Users can view card tags in their orgs" |
||||||
|
ON card_tags FOR SELECT |
||||||
|
USING ( |
||||||
|
tag_id IN ( |
||||||
|
SELECT id FROM tags WHERE org_id IN ( |
||||||
|
SELECT org_id FROM org_members WHERE user_id = auth.uid() |
||||||
|
) |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
CREATE POLICY "Members can manage card tags" |
||||||
|
ON card_tags FOR ALL |
||||||
|
USING ( |
||||||
|
tag_id IN ( |
||||||
|
SELECT id FROM tags WHERE org_id IN ( |
||||||
|
SELECT org_id FROM org_members WHERE user_id = auth.uid() |
||||||
|
) |
||||||
|
) |
||||||
|
); |
||||||
@ -0,0 +1,83 @@ |
|||||||
|
-- Teams/Roles system for organization structure |
||||||
|
-- Like TipiLAN: infra team, sponsors, etc. |
||||||
|
|
||||||
|
-- Create teams table |
||||||
|
CREATE TABLE IF NOT EXISTS teams ( |
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
||||||
|
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, |
||||||
|
name TEXT NOT NULL, |
||||||
|
description TEXT, |
||||||
|
color TEXT DEFAULT '#00A3E0', |
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(), |
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(), |
||||||
|
UNIQUE(org_id, name) |
||||||
|
); |
||||||
|
|
||||||
|
-- Create team members junction table |
||||||
|
CREATE TABLE IF NOT EXISTS team_members ( |
||||||
|
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE, |
||||||
|
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, |
||||||
|
role TEXT DEFAULT 'member' CHECK (role IN ('lead', 'member')), |
||||||
|
joined_at TIMESTAMPTZ DEFAULT NOW(), |
||||||
|
PRIMARY KEY (team_id, user_id) |
||||||
|
); |
||||||
|
|
||||||
|
-- Create indexes |
||||||
|
CREATE INDEX IF NOT EXISTS idx_teams_org ON teams(org_id); |
||||||
|
CREATE INDEX IF NOT EXISTS idx_team_members_team ON team_members(team_id); |
||||||
|
CREATE INDEX IF NOT EXISTS idx_team_members_user ON team_members(user_id); |
||||||
|
|
||||||
|
-- Enable RLS |
||||||
|
ALTER TABLE teams ENABLE ROW LEVEL SECURITY; |
||||||
|
ALTER TABLE team_members ENABLE ROW LEVEL SECURITY; |
||||||
|
|
||||||
|
-- RLS policies for teams |
||||||
|
CREATE POLICY "Users can view teams in their orgs" |
||||||
|
ON teams FOR SELECT |
||||||
|
USING ( |
||||||
|
org_id IN ( |
||||||
|
SELECT org_id FROM org_members WHERE user_id = auth.uid() |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
CREATE POLICY "Admins can manage teams" |
||||||
|
ON teams FOR ALL |
||||||
|
USING ( |
||||||
|
org_id IN ( |
||||||
|
SELECT org_id FROM org_members |
||||||
|
WHERE user_id = auth.uid() |
||||||
|
AND role IN ('owner', 'admin') |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
-- RLS policies for team_members |
||||||
|
CREATE POLICY "Users can view team members in their orgs" |
||||||
|
ON team_members FOR SELECT |
||||||
|
USING ( |
||||||
|
team_id IN ( |
||||||
|
SELECT id FROM teams WHERE org_id IN ( |
||||||
|
SELECT org_id FROM org_members WHERE user_id = auth.uid() |
||||||
|
) |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
CREATE POLICY "Admins can manage team members" |
||||||
|
ON team_members FOR ALL |
||||||
|
USING ( |
||||||
|
team_id IN ( |
||||||
|
SELECT id FROM teams WHERE org_id IN ( |
||||||
|
SELECT org_id FROM org_members |
||||||
|
WHERE user_id = auth.uid() |
||||||
|
AND role IN ('owner', 'admin') |
||||||
|
) |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
-- Add team_id to kanban_boards for team-specific boards |
||||||
|
ALTER TABLE kanban_boards ADD COLUMN IF NOT EXISTS team_id UUID REFERENCES teams(id) ON DELETE SET NULL; |
||||||
|
ALTER TABLE kanban_boards ADD COLUMN IF NOT EXISTS is_personal BOOLEAN DEFAULT FALSE; |
||||||
|
ALTER TABLE kanban_boards ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES profiles(id); |
||||||
|
|
||||||
|
-- Create index for team boards |
||||||
|
CREATE INDEX IF NOT EXISTS idx_kanban_boards_team ON kanban_boards(team_id); |
||||||
|
CREATE INDEX IF NOT EXISTS idx_kanban_boards_personal ON kanban_boards(is_personal, created_by); |
||||||
Loading…
Reference in new issue