diff --git a/apps/db/supabase/migrations/20250620145741_add_calendar_tasks_table.sql b/apps/db/supabase/migrations/20250620145741_add_calendar_tasks_table.sql new file mode 100644 index 000000000..5e5a853a5 --- /dev/null +++ b/apps/db/supabase/migrations/20250620145741_add_calendar_tasks_table.sql @@ -0,0 +1,98 @@ +create type "public"."calendar_task_time" as enum ('working_time', 'personal_time'); + +create type "public"."priority_status" as enum ('low', 'medium', 'high', 'critical'); + +create table "public"."workspace_calendar_tasks" ( + "id" uuid not null default gen_random_uuid(), + "created_at" timestamp with time zone not null default now(), + "ws_id" uuid, + "creator_id" uuid, + "updated_at" timestamp with time zone, + "is_splittable" boolean, + "name" text, + "min_split_duration_minutes" smallint, + "max_split_duration_minutes" smallint, + "schedule_after" timestamp with time zone, + "due_date" timestamp with time zone, + "time_reference" calendar_task_time default 'working_time'::calendar_task_time, + "user_defined_priority" priority_status default 'medium'::priority_status, + "evaluated_priority" priority_status default 'medium'::priority_status +); + + +alter table "public"."workspace_calendar_tasks" enable row level security; + +CREATE UNIQUE INDEX workspace_calendar_tasks_pkey ON public.workspace_calendar_tasks USING btree (id); + +alter table "public"."workspace_calendar_tasks" add constraint "workspace_calendar_tasks_pkey" PRIMARY KEY using index "workspace_calendar_tasks_pkey"; + +alter table "public"."workspace_calendar_tasks" add constraint "workspace_calendar_tasks_creator_id_fkey" FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE not valid; + +alter table "public"."workspace_calendar_tasks" validate constraint "workspace_calendar_tasks_creator_id_fkey"; + +alter table "public"."workspace_calendar_tasks" add constraint "workspace_calendar_tasks_ws_id_fkey" FOREIGN KEY (ws_id) REFERENCES workspaces(id) ON DELETE CASCADE not valid; + +alter table "public"."workspace_calendar_tasks" validate constraint "workspace_calendar_tasks_ws_id_fkey"; + +grant delete on table "public"."workspace_calendar_tasks" to "anon"; + +grant insert on table "public"."workspace_calendar_tasks" to "anon"; + +grant references on table "public"."workspace_calendar_tasks" to "anon"; + +grant select on table "public"."workspace_calendar_tasks" to "anon"; + +grant trigger on table "public"."workspace_calendar_tasks" to "anon"; + +grant truncate on table "public"."workspace_calendar_tasks" to "anon"; + +grant update on table "public"."workspace_calendar_tasks" to "anon"; + +grant delete on table "public"."workspace_calendar_tasks" to "authenticated"; + +grant insert on table "public"."workspace_calendar_tasks" to "authenticated"; + +grant references on table "public"."workspace_calendar_tasks" to "authenticated"; + +grant select on table "public"."workspace_calendar_tasks" to "authenticated"; + +grant trigger on table "public"."workspace_calendar_tasks" to "authenticated"; + +grant truncate on table "public"."workspace_calendar_tasks" to "authenticated"; + +grant update on table "public"."workspace_calendar_tasks" to "authenticated"; + +grant delete on table "public"."workspace_calendar_tasks" to "service_role"; + +grant insert on table "public"."workspace_calendar_tasks" to "service_role"; + +grant references on table "public"."workspace_calendar_tasks" to "service_role"; + +grant select on table "public"."workspace_calendar_tasks" to "service_role"; + +grant trigger on table "public"."workspace_calendar_tasks" to "service_role"; + +grant truncate on table "public"."workspace_calendar_tasks" to "service_role"; + +grant update on table "public"."workspace_calendar_tasks" to "service_role"; + +alter table "public"."workspace_calendar_tasks" add column "description" text; + +alter table "public"."workspace_calendar_tasks" add column "total_duration" text not null; + +alter table "public"."workspace_calendar_tasks" alter column "max_split_duration_minutes" set data type real using "max_split_duration_minutes"::real; + +alter table "public"."workspace_calendar_tasks" alter column "min_split_duration_minutes" set data type real using "min_split_duration_minutes"::real; + +alter table "public"."workspace_calendar_tasks" enable row level security; + +create policy "allow only user in the workspace to insert" +on "public"."workspace_calendar_tasks" +as permissive +for insert +to authenticated +with check (true); + +alter table "public"."workspace_calendar_tasks" alter column "total_duration" set data type real using "total_duration"::real; + +alter table "public"."workspace_calendar_tasks" alter column "updated_at" set default now(); \ No newline at end of file diff --git a/apps/db/supabase/migrations/20250621070438_alter_tasks_table.sql b/apps/db/supabase/migrations/20250621070438_alter_tasks_table.sql new file mode 100644 index 000000000..ddb453391 --- /dev/null +++ b/apps/db/supabase/migrations/20250621070438_alter_tasks_table.sql @@ -0,0 +1,209 @@ +drop policy "Users can view sync logs for their workspaces" on "public"."workspace_calendar_sync_log"; + +drop policy "Workspace members can read and write whiteboards" on "public"."workspace_whiteboards"; + +revoke delete on table "public"."workspace_calendar_sync_log" from "anon"; + +revoke insert on table "public"."workspace_calendar_sync_log" from "anon"; + +revoke references on table "public"."workspace_calendar_sync_log" from "anon"; + +revoke select on table "public"."workspace_calendar_sync_log" from "anon"; + +revoke trigger on table "public"."workspace_calendar_sync_log" from "anon"; + +revoke truncate on table "public"."workspace_calendar_sync_log" from "anon"; + +revoke update on table "public"."workspace_calendar_sync_log" from "anon"; + +revoke delete on table "public"."workspace_calendar_sync_log" from "authenticated"; + +revoke insert on table "public"."workspace_calendar_sync_log" from "authenticated"; + +revoke references on table "public"."workspace_calendar_sync_log" from "authenticated"; + +revoke select on table "public"."workspace_calendar_sync_log" from "authenticated"; + +revoke trigger on table "public"."workspace_calendar_sync_log" from "authenticated"; + +revoke truncate on table "public"."workspace_calendar_sync_log" from "authenticated"; + +revoke update on table "public"."workspace_calendar_sync_log" from "authenticated"; + +revoke delete on table "public"."workspace_calendar_sync_log" from "service_role"; + +revoke insert on table "public"."workspace_calendar_sync_log" from "service_role"; + +revoke references on table "public"."workspace_calendar_sync_log" from "service_role"; + +revoke select on table "public"."workspace_calendar_sync_log" from "service_role"; + +revoke trigger on table "public"."workspace_calendar_sync_log" from "service_role"; + +revoke truncate on table "public"."workspace_calendar_sync_log" from "service_role"; + +revoke update on table "public"."workspace_calendar_sync_log" from "service_role"; + +revoke delete on table "public"."workspace_whiteboards" from "anon"; + +revoke insert on table "public"."workspace_whiteboards" from "anon"; + +revoke references on table "public"."workspace_whiteboards" from "anon"; + +revoke select on table "public"."workspace_whiteboards" from "anon"; + +revoke trigger on table "public"."workspace_whiteboards" from "anon"; + +revoke truncate on table "public"."workspace_whiteboards" from "anon"; + +revoke update on table "public"."workspace_whiteboards" from "anon"; + +revoke delete on table "public"."workspace_whiteboards" from "authenticated"; + +revoke insert on table "public"."workspace_whiteboards" from "authenticated"; + +revoke references on table "public"."workspace_whiteboards" from "authenticated"; + +revoke select on table "public"."workspace_whiteboards" from "authenticated"; + +revoke trigger on table "public"."workspace_whiteboards" from "authenticated"; + +revoke truncate on table "public"."workspace_whiteboards" from "authenticated"; + +revoke update on table "public"."workspace_whiteboards" from "authenticated"; + +revoke delete on table "public"."workspace_whiteboards" from "service_role"; + +revoke insert on table "public"."workspace_whiteboards" from "service_role"; + +revoke references on table "public"."workspace_whiteboards" from "service_role"; + +revoke select on table "public"."workspace_whiteboards" from "service_role"; + +revoke trigger on table "public"."workspace_whiteboards" from "service_role"; + +revoke truncate on table "public"."workspace_whiteboards" from "service_role"; + +revoke update on table "public"."workspace_whiteboards" from "service_role"; + +alter table "public"."workspace_calendar_sync_log" drop constraint "workspace_calendar_sync_log_status_check"; + +alter table "public"."workspace_calendar_sync_log" drop constraint "workspace_calendar_sync_log_timestamps_check"; + +alter table "public"."workspace_calendar_sync_log" drop constraint "workspace_calendar_sync_log_triggered_by_check"; + +alter table "public"."workspace_calendar_sync_log" drop constraint "workspace_calendar_sync_log_ws_id_fkey"; + +alter table "public"."workspace_calendar_tasks" drop constraint "workspace_calendar_tasks_creator_id_fkey"; + +alter table "public"."workspace_calendar_tasks" drop constraint "workspace_calendar_tasks_ws_id_fkey"; + +alter table "public"."workspace_whiteboards" drop constraint "workspace_whiteboards_creator_id_fkey"; + +alter table "public"."workspace_whiteboards" drop constraint "workspace_whiteboards_ws_id_fkey"; + +drop view if exists "public"."time_tracking_session_analytics"; + +alter table "public"."workspace_calendar_sync_log" drop constraint "workspace_calendar_sync_log_pkey"; + +alter table "public"."workspace_calendar_tasks" drop constraint "workspace_calendar_tasks_pkey"; + +alter table "public"."workspace_whiteboards" drop constraint "workspace_whiteboards_pkey"; + +drop index if exists "public"."idx_whiteboards_creator_id"; + +drop index if exists "public"."idx_whiteboards_snapshot_gin"; + +drop index if exists "public"."idx_whiteboards_ws_id"; + +drop index if exists "public"."workspace_calendar_sync_log_pkey"; + +drop index if exists "public"."workspace_calendar_sync_log_status_idx"; + +drop index if exists "public"."workspace_calendar_sync_log_sync_started_at_idx"; + +drop index if exists "public"."workspace_calendar_sync_log_workspace_id_idx"; + +drop index if exists "public"."workspace_calendar_tasks_pkey"; + +drop index if exists "public"."workspace_whiteboards_pkey"; + +drop table "public"."workspace_calendar_sync_log"; + +drop table "public"."workspace_whiteboards"; + +alter table "public"."tasks" add column "is_splittable" boolean not null default true; + +alter table "public"."tasks" add column "max_split_duration_minutes" real default '240'::real; + +alter table "public"."tasks" add column "min_split_duration_minutes" real default '30'::real; + +alter table "public"."tasks" add column "time_reference" calendar_task_time not null default 'working_time'::calendar_task_time; + +alter table "public"."tasks" add column "total_duration" real; + +alter table "public"."tasks" add column "user_defined_priority" priority_status default 'medium'::priority_status; + + + + +CREATE UNIQUE INDEX workspace_calendar_taskss_pkey ON public.workspace_calendar_tasks USING btree (id); + +alter table "public"."workspace_calendar_tasks" add constraint "workspace_calendar_taskss_pkey" PRIMARY KEY using index "workspace_calendar_taskss_pkey"; + +alter table "public"."workspace_calendar_tasks" add constraint "workspace_calendar_taskss_creator_id_fkey" FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE not valid; + +alter table "public"."workspace_calendar_tasks" validate constraint "workspace_calendar_taskss_creator_id_fkey"; + +alter table "public"."workspace_calendar_tasks" add constraint "workspace_calendar_taskss_ws_id_fkey" FOREIGN KEY (ws_id) REFERENCES workspaces(id) ON DELETE CASCADE not valid; + +alter table "public"."workspace_calendar_tasks" validate constraint "workspace_calendar_taskss_ws_id_fkey"; + +create or replace view "public"."time_tracking_session_analytics" as SELECT tts.id, + tts.ws_id, + tts.user_id, + tts.task_id, + tts.category_id, + tts.title, + tts.description, + tts.start_time, + tts.end_time, + tts.duration_seconds, + tts.is_running, + tts.tags, + tts.created_at, + tts.updated_at, + tts.productivity_score, + tts.was_resumed, + ttc.name AS category_name, + ttc.color AS category_color, + t.name AS task_name, + EXTRACT(hour FROM tts.start_time) AS start_hour, + EXTRACT(dow FROM tts.start_time) AS day_of_week, + date_trunc('day'::text, tts.start_time) AS session_date, + date_trunc('week'::text, tts.start_time) AS session_week, + date_trunc('month'::text, tts.start_time) AS session_month, + CASE + WHEN (tts.duration_seconds >= 7200) THEN 'long'::text + WHEN (tts.duration_seconds >= 1800) THEN 'medium'::text + WHEN (tts.duration_seconds >= 300) THEN 'short'::text + ELSE 'micro'::text + END AS session_length_category + FROM ((time_tracking_sessions tts + LEFT JOIN time_tracking_categories ttc ON ((tts.category_id = ttc.id))) + LEFT JOIN tasks t ON ((tts.task_id = t.id))); + + + +alter table "public"."tasks" alter column "is_splittable" drop default; + +alter table "public"."tasks" alter column "is_splittable" drop not null; + +alter table "public"."tasks" alter column "max_split_duration_minutes" drop default; + +alter table "public"."tasks" alter column "min_split_duration_minutes" drop default; + +alter table "public"."tasks" alter column "time_reference" drop default; + +alter table "public"."tasks" alter column "time_reference" drop not null; + diff --git a/apps/db/supabase/migrations/20250621074218_new_migration.sql b/apps/db/supabase/migrations/20250621074218_new_migration.sql new file mode 100644 index 000000000..20ab0a00d --- /dev/null +++ b/apps/db/supabase/migrations/20250621074218_new_migration.sql @@ -0,0 +1,50 @@ + + + + + + + + +alter table "public"."tasks" alter column "list_id" drop not null; + + + + + +create or replace view "public"."time_tracking_session_analytics" as SELECT tts.id, + tts.ws_id, + tts.user_id, + tts.task_id, + tts.category_id, + tts.title, + tts.description, + tts.start_time, + tts.end_time, + tts.duration_seconds, + tts.is_running, + tts.tags, + tts.created_at, + tts.updated_at, + tts.productivity_score, + tts.was_resumed, + ttc.name AS category_name, + ttc.color AS category_color, + t.name AS task_name, + EXTRACT(hour FROM tts.start_time) AS start_hour, + EXTRACT(dow FROM tts.start_time) AS day_of_week, + date_trunc('day'::text, tts.start_time) AS session_date, + date_trunc('week'::text, tts.start_time) AS session_week, + date_trunc('month'::text, tts.start_time) AS session_month, + CASE + WHEN (tts.duration_seconds >= 7200) THEN 'long'::text + WHEN (tts.duration_seconds >= 1800) THEN 'medium'::text + WHEN (tts.duration_seconds >= 300) THEN 'short'::text + ELSE 'micro'::text + END AS session_length_category + FROM ((time_tracking_sessions tts + LEFT JOIN time_tracking_categories ttc ON ((tts.category_id = ttc.id))) + LEFT JOIN tasks t ON ((tts.task_id = t.id))); + + + diff --git a/apps/db/supabase/migrations/20250621082137_new_migration.sql b/apps/db/supabase/migrations/20250621082137_new_migration.sql new file mode 100644 index 000000000..3f0765d66 --- /dev/null +++ b/apps/db/supabase/migrations/20250621082137_new_migration.sql @@ -0,0 +1,41 @@ +drop view if exists "public"."time_tracking_session_analytics"; + + + + +create or replace view "public"."time_tracking_session_analytics" as SELECT tts.id, + tts.ws_id, + tts.user_id, + tts.task_id, + tts.category_id, + tts.title, + tts.description, + tts.start_time, + tts.end_time, + tts.duration_seconds, + tts.is_running, + tts.tags, + tts.created_at, + tts.updated_at, + tts.productivity_score, + tts.was_resumed, + ttc.name AS category_name, + ttc.color AS category_color, + t.name AS task_name, + EXTRACT(hour FROM tts.start_time) AS start_hour, + EXTRACT(dow FROM tts.start_time) AS day_of_week, + date_trunc('day'::text, tts.start_time) AS session_date, + date_trunc('week'::text, tts.start_time) AS session_week, + date_trunc('month'::text, tts.start_time) AS session_month, + CASE + WHEN (tts.duration_seconds >= 7200) THEN 'long'::text + WHEN (tts.duration_seconds >= 1800) THEN 'medium'::text + WHEN (tts.duration_seconds >= 300) THEN 'short'::text + ELSE 'micro'::text + END AS session_length_category + FROM ((time_tracking_sessions tts + LEFT JOIN time_tracking_categories ttc ON ((tts.category_id = ttc.id))) + LEFT JOIN tasks t ON ((tts.task_id = t.id))); + + + diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx index 079c9afd0..4031eb845 100644 --- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx @@ -1,5 +1,7 @@ 'use client'; +import AddEventButton from './components/add-event-button'; +import AddEventModal from './components/add-event-dialog'; import AutoScheduleComprehensiveDialog from './components/auto-schedule-comprehensive-dialog'; import TestEventGeneratorButton from './components/test-event-generator-button'; import { DEV_MODE } from '@/constants/common'; @@ -10,6 +12,7 @@ import { Sparkles } from '@tuturuuu/ui/icons'; import { SmartCalendar } from '@tuturuuu/ui/legacy/calendar/smart-calendar'; import { ROOT_WORKSPACE_ID } from '@tuturuuu/utils/constants'; import { useLocale, useTranslations } from 'next-intl'; +import { useState } from 'react'; export default function CalendarClientPage({ experimentalGoogleToken, @@ -20,11 +23,16 @@ export default function CalendarClientPage({ }) { const t = useTranslations('calendar'); const locale = useLocale(); + const [isAddEventModalOpen, setIsAddEventModalOpen] = useState(false); + + const openAddEventDialog = () => setIsAddEventModalOpen(true); + const closeAddEventDialog = () => setIsAddEventModalOpen(false); const extras = workspace.id === ROOT_WORKSPACE_ID ? (