Add availabilities

This commit is contained in:
Arthur Belleville 2025-09-09 22:49:02 +02:00
parent 223a7d5cad
commit 90b7e04473
No known key found for this signature in database
8 changed files with 1240 additions and 6 deletions

View file

@ -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<Database, { PostgrestVersion: 'XX' }>(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
}
}
}
}

View file

@ -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.';

View file

@ -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 (
<div className="flex flex-col gap-2 w-full">
<div className="flex items-center justify-between">
<Text className="text-lg font-semibold">{dayDisplay}</Text>
</div>
<div className="flex items-center gap-2">
<Switch
isSelected={enabled}
onChange={onEnabledChange}
className="data-[selected=true]:bg-primary"
>
<Text
className={`font-medium text-sm ${
enabled ? "text-gray-900" : "text-gray-500"
}`}
>
{enabled ? "Disponible" : "Indisponible"}
</Text>
</Switch>
</div>
<div className="flex gap-1.5 flex-wrap items-center">
{timeRanges.map((range, index) => (
<div
key={index}
className="flex items-center gap-1 bg-gray-50 dark:bg-gray-900 rounded px-1.5 py-0.5"
>
<div className="flex gap-0.5 items-center text-xs whitespace-nowrap">
<Text className="font-medium text-xs">{range.start}</Text>
<Text className="text-gray-500 text-xs">-</Text>
<Text className="font-medium text-xs">{range.end}</Text>
</div>
{timeRanges.length > 1 && (
<Button
onPress={() => handleDeleteRange(index)}
isDisabled={!enabled}
variant="outline"
size="sm"
isIconOnly
className="h-5 w-5 p-0 ml-0.5 border-rose-200 hover:border-rose-300 hover:bg-rose-50 dark:border-rose-800 dark:hover:border-rose-700 dark:hover:bg-rose-950/30 text-rose-600 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-300"
>
<MinusIcon className="size-2.5" />
</Button>
)}
</div>
))}
{timeRanges.length < 3 && (
<Button
onPress={() => handleAddRange()}
isDisabled={!enabled}
variant="outline"
size="sm"
className="h-6 px-1.5 flex items-center gap-1 text-xs"
>
<PlusIcon className="size-2.5" />
</Button>
)}
</div>
<Slider
value={value}
onChange={handleChange}
minValue={0}
maxValue={MINUTES_IN_DAY}
step={30}
isDisabled={!enabled}
className="w-full"
thumbLabels={["Début", "Fin"]}
label={`${dayDisplay} (${currentRange.start} - ${currentRange.end})`}
>
<SliderTrack thumbLabels={["Début", "Fin"]} />
</Slider>
</div>
);
}

View file

@ -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<WeeklyAvailability>({
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<WeeklyAvailability | null>(null);
useEffect(() => {
if (availabilities) {
setDraftAvailabilities(availabilities);
}
}, [availabilities]);
return {
isLoading,
updateAvailabilities,
draftAvailabilities: draftAvailabilities || DEFAULT_AVAILABILITIES,
setDraftAvailabilities,
isUpdating,
};
}

View file

@ -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: <AvailabilitiesPage />,
},
{
path: "feedback",
element: <FeedbackPage />,

View file

@ -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 (
<div className="h-full flex flex-col p-4">
<div className="flex justify-between items-start mb-3">
<div>
<h2 className="text-2xl font-bold">Disponibilités</h2>
<Strong className="text-gray-500 mt-2 text-xl">
Définissez vos horaires de disponibilité pour chaque jour de la
semaine
</Strong>
</div>
<div className="flex gap-2">
<Button
size="lg"
onPress={() => {
updateAvailabilities(DEFAULT_AVAILABILITIES);
}}
className="py-1"
>
Horaires de bureau (9h-17h)
</Button>
<Button
size="lg"
variant="outline"
onPress={() => {
const newAvailabilities: WeeklyAvailability = {};
DAYS_OF_WEEK.forEach((day) => {
newAvailabilities[day] = {
enabled: false,
timeRanges: [{ start: "09:00", end: "17:00" }],
};
});
updateAvailabilities(newAvailabilities);
}}
className="py-1"
>
Tout désactiver
</Button>
</div>
</div>
<div className="flex-1 overflow-auto">
<div className="flex">
<div className="flex-1 pr-6 border-r border-gray-200 dark:border-gray-700">
<div className="grid grid-cols-2 gap-4 max-w-4xl min-h-min">
{DAYS_OF_WEEK.map((day) => (
<div
key={day}
className="bg-white dark:bg-gray-800 rounded-lg shadow p-2"
>
<div className="space-y-2">
<AvailabilityCard
day={day}
enabled={draftAvailabilities[day].enabled}
onEnabledChange={(enabled) => {
setDraftAvailabilities({
...draftAvailabilities,
[day]: {
...draftAvailabilities[day],
enabled,
},
});
}}
timeRanges={draftAvailabilities[day].timeRanges}
onTimeRangesChange={(ranges) => {
setDraftAvailabilities({
...draftAvailabilities,
[day]: {
...draftAvailabilities[day],
timeRanges: ranges,
},
});
}}
/>
</div>
</div>
))}
</div>
</div>
<div className="w-80 pl-6 py-4">
<div className="space-y-6">
<div>
<h3 className="text-xl font-semibold mb-2">Fuseau horaire</h3>
<Text className="text-gray-500">
Vos disponibilités sont affichées dans votre fuseau horaire
local.
</Text>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
<Strong className="block mb-2">Votre fuseau horaire</Strong>
<Text className="text-gray-500">
{Intl.DateTimeFormat().resolvedOptions().timeZone}
</Text>
<Text className="text-sm text-gray-400 mt-2">
{new Date().toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}{" "}
- Heure locale
</Text>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
<Strong className="block mb-2">Information</Strong>
<Text className="text-gray-500 text-sm">
Les créneaux horaires seront automatiquement convertis dans le
fuseau horaire de vos clients lorsqu&apos;ils consulteront vos
disponibilités.
</Text>
</div>
</div>
</div>
</div>
</div>
<div className="flex justify-end gap-4 items-center border-t pt-4">
{isUpdating && <LoadingSpinner />}
<Button
size="lg"
variant="solid"
isDisabled={isUpdating}
onPress={() => {
updateAvailabilities(draftAvailabilities, {
onSuccess: () => {
toast.add({
title: "Succès",
description: "Disponibilités enregistrées avec succès",
type: "success",
});
},
onError: () => {
toast.add({
title: "Erreur",
description:
"Erreur lors de l'enregistrement des disponibilités",
type: "error",
});
},
});
}}
>
{isUpdating ? "Enregistrement..." : "Enregistrer les disponibilités"}
</Button>
</div>
</div>
);
}

View file

@ -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<Database, { PostgrestVersion: 'XX' }>(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
}
}
}
}

View file

@ -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<Database, { PostgrestVersion: 'XX' }>(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<Database, "__InternalSupabase">
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, "public">]
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