diff --git a/api/src/database.types.ts b/api/src/database.types.ts index 51e1eed..e0b0a58 100644 --- a/api/src/database.types.ts +++ b/api/src/database.types.ts @@ -7,13 +7,80 @@ export type Json = | Json[] export type Database = { - // Allows to automatically instanciate createClient with right options + // Allows to automatically instantiate createClient with right options // instead of createClient(URL, KEY) __InternalSupabase: { - PostgrestVersion: "12.2.3 (519615d)" + PostgrestVersion: "13.0.4" } public: { Tables: { + availabilities: { + Row: { + availability_data: Json + created_at: string + id: number + updated_at: string + user_id: string + } + Insert: { + availability_data?: Json + created_at?: string + id?: number + updated_at?: string + user_id: string + } + Update: { + availability_data?: Json + created_at?: string + id?: number + updated_at?: string + user_id?: string + } + Relationships: [] + } + calendar_subscriptions: { + Row: { + created_at: string | null + id: string + tablo_id: string + token: string + } + Insert: { + created_at?: string | null + id?: string + tablo_id: string + token: string + } + Update: { + created_at?: string | null + id?: string + tablo_id?: string + token?: string + } + Relationships: [ + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "events_and_tablos" + referencedColumns: ["tablo_id"] + }, + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "tablos" + referencedColumns: ["id"] + }, + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "user_tablos" + referencedColumns: ["id"] + }, + ] + } devis: { Row: { client_email: string @@ -367,7 +434,10 @@ export type Database = { devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired" } CompositeTypes: { - [_ in never]: never + time_range: { + start_time: string | null + end_time: string | null + } } } } diff --git a/sql/17_availabilities_table.sql b/sql/17_availabilities_table.sql new file mode 100644 index 0000000..8f3a12a --- /dev/null +++ b/sql/17_availabilities_table.sql @@ -0,0 +1,69 @@ +-- Create the availabilities table +CREATE TABLE IF NOT EXISTS availabilities ( + id SERIAL PRIMARY KEY, + user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + availabilities JSONB NOT NULL DEFAULT '{}'::jsonb, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); + +-- Create an index for faster lookups by user_id +CREATE INDEX IF NOT EXISTS idx_availabilities_user_id ON availabilities(user_id); + +-- Add unique constraint on user_id to ensure one availability record per user +ALTER TABLE availabilities ADD CONSTRAINT unique_user_availabilities UNIQUE (user_id); + + +-- Add trigger to update updated_at timestamp +CREATE TRIGGER update_availabilities_updated_at + BEFORE UPDATE ON availabilities + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- Enable Row Level Security +ALTER TABLE availabilities ENABLE ROW LEVEL SECURITY; + +-- Policy to allow users to view their own availabilities +CREATE POLICY "Users can view their own availabilities" ON availabilities + FOR SELECT + TO authenticated + USING (user_id = auth.uid()); + +-- Policy to allow users to insert their own availabilities +CREATE POLICY "Users can insert their own availabilities" ON availabilities + FOR INSERT + TO authenticated + WITH CHECK (user_id = auth.uid()); + +-- Policy to allow users to update their own availabilities +CREATE POLICY "Users can update their own availabilities" ON availabilities + FOR UPDATE + TO authenticated + USING (user_id = auth.uid()) + WITH CHECK (user_id = auth.uid()); + +-- Policy to allow users to delete their own availabilities +CREATE POLICY "Users can delete their own availabilities" ON availabilities + FOR DELETE + TO authenticated + USING (user_id = auth.uid()); + +-- Add helpful comments +COMMENT ON TABLE availabilities IS + 'User availability settings with Row Level Security'; + +COMMENT ON COLUMN availabilities.id IS + 'Primary key: auto-incrementing integer'; + +COMMENT ON COLUMN availabilities.user_id IS + 'Foreign key reference to auth.users(id)'; + +COMMENT ON COLUMN availabilities.availabilities IS + 'JSONB object containing availability settings for each day (0-6, where 0 is Monday). Each day has enabled status and time ranges.'; + +-- Rename the availabilities column to availability_data for clarity +ALTER TABLE availabilities RENAME COLUMN availabilities TO availability_data; + +-- Update the comment for the renamed column +COMMENT ON COLUMN availabilities.availability_data IS + 'JSONB object containing availability settings for each day (0-6, where 0 is Monday). Each day has enabled status and time ranges.'; diff --git a/ui/src/components/AvailabilityCard.tsx b/ui/src/components/AvailabilityCard.tsx new file mode 100644 index 0000000..57487e2 --- /dev/null +++ b/ui/src/components/AvailabilityCard.tsx @@ -0,0 +1,194 @@ +import { useState } from "react"; +import { Switch } from "@ui/ui-library/switch"; +import { Text } from "@ui/ui-library/text"; +import { Slider, SliderTack as SliderTrack } from "@ui/ui-library/slider"; +import { Button } from "@ui/ui-library/button"; +import { MinusIcon, PlusIcon } from "@ui/ui-library/icons"; + +interface TimeRange { + start: string; + end: string; +} + +interface AvailabilityCardProps { + day: number; + enabled: boolean; + onEnabledChange: (enabled: boolean) => void; + timeRanges: TimeRange[]; + onTimeRangesChange: (ranges: TimeRange[]) => void; +} + +const MINUTES_IN_DAY = 24 * 60; + +const DAYS_OF_WEEK_DISPLAY = [ + "Lundi", + "Mardi", + "Mercredi", + "Jeudi", + "Vendredi", + "Samedi", + "Dimanche", +]; + +function timeToMinutes(time: string): number { + const [hours, minutes] = time.split(":").map(Number); + return hours * 60 + minutes; +} + +function minutesToTime(minutes: number): string { + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + return `${hours.toString().padStart(2, "0")}:${mins + .toString() + .padStart(2, "0")}`; +} + +export function AvailabilityCard({ + day, + enabled, + onEnabledChange, + timeRanges, + onTimeRangesChange, +}: AvailabilityCardProps) { + const dayDisplay = DAYS_OF_WEEK_DISPLAY[day]; + + const [selectedRangeIndex, setSelectedRangeIndex] = useState(0); + + const handleAddRange = () => { + // Find a free slot for the new range + const sortedRanges = [...timeRanges].sort( + (a, b) => timeToMinutes(a.start) - timeToMinutes(b.start) + ); + let newStart = "09:00"; + let newEnd = "17:00"; + + for (let i = 0; i < sortedRanges.length; i++) { + const currentRange = sortedRanges[i]; + const nextRange = sortedRanges[i + 1]; + + if (!nextRange) { + // If this is the last range, add new range after it + const currentEnd = timeToMinutes(currentRange.end); + if (currentEnd + 120 <= MINUTES_IN_DAY) { + // At least 2 hours before end of day + newStart = minutesToTime(currentEnd + 30); + newEnd = minutesToTime(Math.min(currentEnd + 240, MINUTES_IN_DAY)); // 4 hours or end of day + } + break; + } + + const gap = + timeToMinutes(nextRange.start) - timeToMinutes(currentRange.end); + if (gap >= 120) { + // At least 2 hours gap + newStart = minutesToTime(timeToMinutes(currentRange.end) + 30); + newEnd = minutesToTime(timeToMinutes(nextRange.start) - 30); + break; + } + } + + const newRanges = [...timeRanges, { start: newStart, end: newEnd }]; + onTimeRangesChange(newRanges); + setSelectedRangeIndex(newRanges.length - 1); + }; + + const handleDeleteRange = (index: number) => { + const newRanges = timeRanges.filter((_, i) => i !== index); + onTimeRangesChange(newRanges); + setSelectedRangeIndex(Math.min(selectedRangeIndex, newRanges.length - 1)); + }; + + const currentRange = timeRanges[selectedRangeIndex] || { + start: "09:00", + end: "17:00", + }; + const value = [ + timeToMinutes(currentRange.start), + timeToMinutes(currentRange.end), + ]; + + const handleChange = (newValue: number[]) => { + const [start, end] = newValue; + const newRanges = [...timeRanges]; + newRanges[selectedRangeIndex] = { + start: minutesToTime(start), + end: minutesToTime(end), + }; + onTimeRangesChange(newRanges); + }; + + return ( +
+
+ {dayDisplay} +
+
+ + + {enabled ? "Disponible" : "Indisponible"} + + +
+ +
+ {timeRanges.map((range, index) => ( +
+
+ {range.start} + - + {range.end} +
+ {timeRanges.length > 1 && ( + + )} +
+ ))} + {timeRanges.length < 3 && ( + + )} +
+ + + + +
+ ); +} diff --git a/ui/src/hooks/availabilities.ts b/ui/src/hooks/availabilities.ts new file mode 100644 index 0000000..0022c9a --- /dev/null +++ b/ui/src/hooks/availabilities.ts @@ -0,0 +1,89 @@ +import { useMutation, useQuery } from "@tanstack/react-query"; +import { queryClient } from "@ui/lib/api"; +import { supabase } from "@ui/hooks/auth"; +import { useSession } from "@ui/contexts/SessionContext"; +import { useEffect, useState } from "react"; + +export type TimeRange = { + start: string; + end: string; +}; + +export type DayAvailability = { + enabled: boolean; + timeRanges: TimeRange[]; +}; + +export type WeeklyAvailability = { + [key: number]: DayAvailability; +}; + +const DAYS_OF_WEEK = [0, 1, 2, 3, 4, 5, 6]; + +export const DEFAULT_AVAILABILITIES: WeeklyAvailability = DAYS_OF_WEEK.reduce( + (acc, day) => { + acc[day] = { + enabled: true, + timeRanges: [{ start: "09:00", end: "17:00" }], + }; + return acc; + }, + {} as WeeklyAvailability +); + +export function useAvailabilities() { + const { session } = useSession(); + + const { data: availabilities, isLoading } = useQuery({ + queryKey: ["availabilities"], + queryFn: async () => { + const { data, error } = await supabase + .from("availabilities") + .select("*") + .eq("user_id", session?.user.id) + .limit(1); + if (error) throw error; + return data?.[0].availability_data as WeeklyAvailability; + }, + enabled: !!session?.user.id, + }); + + console.log("availabilities", availabilities); + + const { mutate: updateAvailabilities, isPending: isUpdating } = useMutation({ + mutationFn: async (optionalAvailabilities: WeeklyAvailability) => { + const newAvailabilities = + optionalAvailabilities || DEFAULT_AVAILABILITIES; + const { error } = await supabase.from("availabilities").upsert( + { + availability_data: newAvailabilities, + user_id: session?.user.id, + }, + { + onConflict: "user_id", + } + ); + if (error) throw error; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["availabilities"] }); + }, + }); + + const [draftAvailabilities, setDraftAvailabilities] = + useState(null); + + useEffect(() => { + if (availabilities) { + setDraftAvailabilities(availabilities); + } + }, [availabilities]); + + return { + isLoading, + updateAvailabilities, + draftAvailabilities: draftAvailabilities || DEFAULT_AVAILABILITIES, + setDraftAvailabilities, + isUpdating, + }; +} diff --git a/ui/src/lib/routes.tsx b/ui/src/lib/routes.tsx index 36a05f1..e7d7f99 100644 --- a/ui/src/lib/routes.tsx +++ b/ui/src/lib/routes.tsx @@ -19,6 +19,7 @@ import { ChantiersPage } from "@ui/pages/chantiers"; import { ChatPage } from "@ui/pages/chat"; import { FeedbackPage } from "@ui/pages/feedback"; import { SupportPage } from "@ui/pages/support"; +import { AvailabilitiesPage } from "@ui/pages/availabilities"; export const routes: RouteObject[] = [ // Protected routes @@ -73,6 +74,10 @@ export const routes: RouteObject[] = [ ), children: [{ index: true }, { path: ":channelId" }], }, + { + path: "availabilities", + element: , + }, { path: "feedback", element: , diff --git a/ui/src/pages/availabilities.tsx b/ui/src/pages/availabilities.tsx new file mode 100644 index 0000000..fa5faba --- /dev/null +++ b/ui/src/pages/availabilities.tsx @@ -0,0 +1,169 @@ +import { Strong, Text } from "@ui/ui-library/text"; +import { AvailabilityCard } from "@ui/components/AvailabilityCard"; +import { Button } from "@ui/ui-library/button"; +import { LoadingSpinner } from "@ui/components/LoadingSpinner"; +import { + DEFAULT_AVAILABILITIES, + useAvailabilities, + WeeklyAvailability, +} from "@ui/hooks/availabilities"; +import { toast } from "@ui/ui-library/toast/toast-queue"; + +const DAYS_OF_WEEK = [0, 1, 2, 3, 4, 5, 6]; + +export function AvailabilitiesPage() { + const { + updateAvailabilities, + isUpdating, + draftAvailabilities, + setDraftAvailabilities, + } = useAvailabilities(); + + return ( +
+
+
+

Disponibilités

+ + Définissez vos horaires de disponibilité pour chaque jour de la + semaine + +
+
+ + +
+
+ +
+
+
+
+ {DAYS_OF_WEEK.map((day) => ( +
+
+ { + setDraftAvailabilities({ + ...draftAvailabilities, + [day]: { + ...draftAvailabilities[day], + enabled, + }, + }); + }} + timeRanges={draftAvailabilities[day].timeRanges} + onTimeRangesChange={(ranges) => { + setDraftAvailabilities({ + ...draftAvailabilities, + [day]: { + ...draftAvailabilities[day], + timeRanges: ranges, + }, + }); + }} + /> +
+
+ ))} +
+
+ +
+
+
+

Fuseau horaire

+ + Vos disponibilités sont affichées dans votre fuseau horaire + local. + +
+ +
+ Votre fuseau horaire + + {Intl.DateTimeFormat().resolvedOptions().timeZone} + + + {new Date().toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })}{" "} + - Heure locale + +
+ +
+ Information + + Les créneaux horaires seront automatiquement convertis dans le + fuseau horaire de vos clients lorsqu'ils consulteront vos + disponibilités. + +
+
+
+
+
+ +
+ {isUpdating && } + +
+
+ ); +} diff --git a/ui/src/types/database.types.ts b/ui/src/types/database.types.ts index 51e1eed..e0b0a58 100644 --- a/ui/src/types/database.types.ts +++ b/ui/src/types/database.types.ts @@ -7,13 +7,80 @@ export type Json = | Json[] export type Database = { - // Allows to automatically instanciate createClient with right options + // Allows to automatically instantiate createClient with right options // instead of createClient(URL, KEY) __InternalSupabase: { - PostgrestVersion: "12.2.3 (519615d)" + PostgrestVersion: "13.0.4" } public: { Tables: { + availabilities: { + Row: { + availability_data: Json + created_at: string + id: number + updated_at: string + user_id: string + } + Insert: { + availability_data?: Json + created_at?: string + id?: number + updated_at?: string + user_id: string + } + Update: { + availability_data?: Json + created_at?: string + id?: number + updated_at?: string + user_id?: string + } + Relationships: [] + } + calendar_subscriptions: { + Row: { + created_at: string | null + id: string + tablo_id: string + token: string + } + Insert: { + created_at?: string | null + id?: string + tablo_id: string + token: string + } + Update: { + created_at?: string | null + id?: string + tablo_id?: string + token?: string + } + Relationships: [ + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "events_and_tablos" + referencedColumns: ["tablo_id"] + }, + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "tablos" + referencedColumns: ["id"] + }, + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "user_tablos" + referencedColumns: ["id"] + }, + ] + } devis: { Row: { client_email: string @@ -367,7 +434,10 @@ export type Database = { devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired" } CompositeTypes: { - [_ in never]: never + time_range: { + start_time: string | null + end_time: string | null + } } } } diff --git a/xtablo-expo/lib/database.types.ts b/xtablo-expo/lib/database.types.ts new file mode 100644 index 0000000..e0b0a58 --- /dev/null +++ b/xtablo-expo/lib/database.types.ts @@ -0,0 +1,568 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + // Allows to automatically instantiate createClient with right options + // instead of createClient(URL, KEY) + __InternalSupabase: { + PostgrestVersion: "13.0.4" + } + public: { + Tables: { + availabilities: { + Row: { + availability_data: Json + created_at: string + id: number + updated_at: string + user_id: string + } + Insert: { + availability_data?: Json + created_at?: string + id?: number + updated_at?: string + user_id: string + } + Update: { + availability_data?: Json + created_at?: string + id?: number + updated_at?: string + user_id?: string + } + Relationships: [] + } + calendar_subscriptions: { + Row: { + created_at: string | null + id: string + tablo_id: string + token: string + } + Insert: { + created_at?: string | null + id?: string + tablo_id: string + token: string + } + Update: { + created_at?: string | null + id?: string + tablo_id?: string + token?: string + } + Relationships: [ + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "events_and_tablos" + referencedColumns: ["tablo_id"] + }, + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "tablos" + referencedColumns: ["id"] + }, + { + foreignKeyName: "calendar_subscriptions_tablo_id_fkey" + columns: ["tablo_id"] + isOneToOne: true + referencedRelation: "user_tablos" + referencedColumns: ["id"] + }, + ] + } + devis: { + Row: { + client_email: string + created_at: string + date: string + due_date: string + id: string + items: Json + notes: string | null + number: string + status: Database["public"]["Enums"]["devis_status"] + subtotal: number + tax: number + terms: string | null + total: number + updated_at: string + user_id: string + } + Insert: { + client_email: string + created_at?: string + date: string + due_date: string + id?: string + items?: Json + notes?: string | null + number: string + status?: Database["public"]["Enums"]["devis_status"] + subtotal: number + tax: number + terms?: string | null + total: number + updated_at?: string + user_id: string + } + Update: { + client_email?: string + created_at?: string + date?: string + due_date?: string + id?: string + items?: Json + notes?: string | null + number?: string + status?: Database["public"]["Enums"]["devis_status"] + subtotal?: number + tax?: number + terms?: string | null + total?: number + updated_at?: string + user_id?: string + } + Relationships: [] + } + events: { + Row: { + created_at: string | null + created_by: string + deleted_at: string | null + description: string | null + end_time: string | null + id: string + start_date: string + start_time: string + tablo_id: string + title: string + } + Insert: { + created_at?: string | null + created_by: string + deleted_at?: string | null + description?: string | null + end_time?: string | null + id?: string + start_date: string + start_time: string + tablo_id: string + title: string + } + Update: { + created_at?: string | null + created_by?: string + deleted_at?: string | null + description?: string | null + end_time?: string | null + id?: string + start_date?: string + start_time?: string + tablo_id?: string + title?: string + } + Relationships: [ + { + foreignKeyName: "fk_events_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "events_and_tablos" + referencedColumns: ["tablo_id"] + }, + { + foreignKeyName: "fk_events_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "tablos" + referencedColumns: ["id"] + }, + { + foreignKeyName: "fk_events_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "user_tablos" + referencedColumns: ["id"] + }, + ] + } + feedbacks: { + Row: { + created_at: string | null + fd_type: string + id: number + message: string + user_id: string + } + Insert: { + created_at?: string | null + fd_type: string + id?: number + message: string + user_id: string + } + Update: { + created_at?: string | null + fd_type?: string + id?: number + message?: string + user_id?: string + } + Relationships: [] + } + profiles: { + Row: { + avatar_url: string | null + email: string | null + id: string + name: string | null + } + Insert: { + avatar_url?: string | null + email?: string | null + id: string + name?: string | null + } + Update: { + avatar_url?: string | null + email?: string | null + id?: string + name?: string | null + } + Relationships: [] + } + tablo_access: { + Row: { + created_at: string | null + granted_by: string + id: number + is_active: boolean | null + is_admin: boolean | null + tablo_id: string + user_id: string + } + Insert: { + created_at?: string | null + granted_by: string + id?: number + is_active?: boolean | null + is_admin?: boolean | null + tablo_id: string + user_id: string + } + Update: { + created_at?: string | null + granted_by?: string + id?: number + is_active?: boolean | null + is_admin?: boolean | null + tablo_id?: string + user_id?: string + } + Relationships: [ + { + foreignKeyName: "fk_tablo_access_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "events_and_tablos" + referencedColumns: ["tablo_id"] + }, + { + foreignKeyName: "fk_tablo_access_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "tablos" + referencedColumns: ["id"] + }, + { + foreignKeyName: "fk_tablo_access_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "user_tablos" + referencedColumns: ["id"] + }, + { + foreignKeyName: "fk_tablo_access_user_id_from_profiles" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + }, + ] + } + tablo_invites: { + Row: { + id: number + invite_token: string + invited_by: string + invited_email: string + tablo_id: string + } + Insert: { + id?: number + invite_token: string + invited_by: string + invited_email: string + tablo_id: string + } + Update: { + id?: number + invite_token?: string + invited_by?: string + invited_email?: string + tablo_id?: string + } + Relationships: [ + { + foreignKeyName: "fk_tablo_invitations_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "events_and_tablos" + referencedColumns: ["tablo_id"] + }, + { + foreignKeyName: "fk_tablo_invitations_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "tablos" + referencedColumns: ["id"] + }, + { + foreignKeyName: "fk_tablo_invitations_tablo_id" + columns: ["tablo_id"] + isOneToOne: false + referencedRelation: "user_tablos" + referencedColumns: ["id"] + }, + ] + } + tablos: { + Row: { + color: string | null + created_at: string | null + deleted_at: string | null + id: string + image: string | null + name: string + owner_id: string + position: number + status: string + } + Insert: { + color?: string | null + created_at?: string | null + deleted_at?: string | null + id?: string + image?: string | null + name: string + owner_id: string + position?: number + status?: string + } + Update: { + color?: string | null + created_at?: string | null + deleted_at?: string | null + id?: string + image?: string | null + name?: string + owner_id?: string + position?: number + status?: string + } + Relationships: [] + } + } + Views: { + events_and_tablos: { + Row: { + description: string | null + end_time: string | null + event_id: string | null + start_date: string | null + start_time: string | null + tablo_color: string | null + tablo_id: string | null + tablo_name: string | null + tablo_status: string | null + title: string | null + } + Relationships: [] + } + user_tablos: { + Row: { + access_level: string | null + color: string | null + created_at: string | null + deleted_at: string | null + id: string | null + image: string | null + is_admin: boolean | null + name: string | null + position: number | null + status: string | null + user_id: string | null + } + Relationships: [ + { + foreignKeyName: "fk_tablo_access_user_id_from_profiles" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + }, + ] + } + } + Functions: { + generate_random_string: { + Args: { length?: number } + Returns: string + } + } + Enums: { + devis_status: "draft" | "sent" | "accepted" | "rejected" | "expired" + } + CompositeTypes: { + time_range: { + start_time: string | null + end_time: string | null + } + } + } +} + +type DatabaseWithoutInternals = Omit + +type DefaultSchema = DatabaseWithoutInternals[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + public: { + Enums: { + devis_status: ["draft", "sent", "accepted", "rejected", "expired"], + }, + }, +} as const