diff --git a/api/supabase/queries/availability.ts b/api/supabase/queries/availability.ts index 4d57d5d..9ac5a78 100644 --- a/api/supabase/queries/availability.ts +++ b/api/supabase/queries/availability.ts @@ -23,3 +23,43 @@ export async function fetchAvailabilitiesByFacilityId(facility_id: UUID) { return null; // Return null on unexpected error } } + +export async function fetchAllAvailabilities() { + try { + const { data, error } = await supabase.from('availabilities').select('*'); + if (error) { + throw new Error(error.message); + } + if (data && data.length == 0) { + console.log('No availabilities found'); + return []; + } + return data; + } catch (error) { + console.error('An unexpected error occured:', error); + return null; + } +} + +export async function fetchDatesByAvailabilityID(availability_id: UUID) { + try { + const { data, error } = await supabase + .from('available_dates') + .select('*') + .eq('availability_id', availability_id); + if (error) { + throw new Error(error.message); + } + if (data && data.length == 0) { + console.log( + 'No dates found for this availability id or availability_id is undefined', + availability_id, + ); + return []; + } + return data; + } catch (error) { + console.error('An unexpected error occured:', error); + return null; + } +} diff --git a/app/(auth)/auth-styles.ts b/app/(auth)/auth-styles.ts index 617990e..b10782a 100644 --- a/app/(auth)/auth-styles.ts +++ b/app/(auth)/auth-styles.ts @@ -79,7 +79,7 @@ export const Input = styled.input` export const Button = styled.button` font-family: ${Sans.style.fontFamily}; - background-color: ${COLORS.pomegranate}; + background-color: ${COLORS.pomegranate12}; color: white; font-size: 1rem; padding: 0.75rem; diff --git a/app/availability/general/page.tsx b/app/availability/general/page.tsx new file mode 100644 index 0000000..0c65c42 --- /dev/null +++ b/app/availability/general/page.tsx @@ -0,0 +1,138 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; +import { + fetchAllAvailabilities, + fetchDatesByAvailabilityID, +} from '@/api/supabase/queries/availability'; +import AvailabilityCard from '@/components/AvailabilityCard/AvailabilityCard'; +import MenuBar from '@/components/MenuBar/MenuBar'; +import Add from '@/public/images/add.svg'; +import COLORS from '@/styles/colors'; +import { H3 } from '@/styles/text'; +import { Availabilities, AvailableDates } from '@/types/schema'; +import * as styles from './styles'; + +type AvailabilitiesByYear = { + [year: string]: { + availability: Availabilities; + available_dates: AvailableDates[]; + }[]; +}; + +export default function AvailabilityPage() { + const [groupedByYear, setGroupedByYear] = useState({}); + const [isLoading, setIsLoading] = useState(true); + const [menuExpanded, setMenuExpanded] = useState(false); // Track the expanded state of the menu + + useEffect(() => { + async function fetchAndGroupData() { + try { + // Step 1: Fetch all availabilities + const availabilities = await fetchAllAvailabilities(); + + if (!availabilities) { + return; + } + + // Step 2: Group by year while fetching associated dates + const grouped: AvailabilitiesByYear = {}; + for (const availability of availabilities) { + const availableDates = await fetchDatesByAvailabilityID( + availability.availability_id, + ); + + const year = availableDates?.[0]?.available_date + ? new Date(availableDates[0].available_date) + .getFullYear() + .toString() + : null; + + //don't display availability cards that have no availabilities associated + if (year == null) { + continue; + } + + if (!grouped[year]) { + grouped[year] = []; + } + + grouped[year].push({ + availability, + available_dates: availableDates ?? [], + }); + } + for (const year in grouped) { + grouped[year].sort((a, b) => { + const firstDateA = new Date( + a.available_dates[0]?.available_date ?? 0, + ).getTime(); + const firstDateB = new Date( + b.available_dates[0]?.available_date ?? 0, + ).getTime(); + return firstDateA - firstDateB; + }); + } + + setGroupedByYear(grouped); + setIsLoading(false); // Stop loading after data fetch + } catch (error) { + console.error('Error fetching data:', error); + } + } + + fetchAndGroupData(); + }, []); + + return ( +
+ {' '} + {/* Pass function to update state */} + + + +

+ Availabilities +

+ +
+ {/* Check if there are no availabilities */} + {isLoading ? ( + + Loading availabilities... + + ) : Object.keys(groupedByYear).length === 0 ? ( + + No availabilities yet, +
+ add one with the plus sign! +
+ ) : ( + Object.entries(groupedByYear).map(([year, availabilities]) => ( +
+ + {year} + + {availabilities.map(({ availability, available_dates }) => ( + + ))} +
+ )) + )} +
+
+
+ ); +} diff --git a/app/availability/general/styles.ts b/app/availability/general/styles.ts new file mode 100644 index 0000000..e219b00 --- /dev/null +++ b/app/availability/general/styles.ts @@ -0,0 +1,45 @@ +'use client'; + +import NextImage from 'next/image'; +import styled from 'styled-components'; +import { H6, P } from '@/styles/text'; + +export const Page = styled.main<{ $menuExpanded: boolean }>` + display: flex; + min-width: 100%; + min-height: 100vh; + justify-content: center; + overflow: hidden; + margin-left: ${({ $menuExpanded }) => + $menuExpanded ? '10%' : '0'}; /* Fixed margin for the expanded menu */ + transition: margin-left 0.3s ease; /* Smooth transition */ +`; + +export const AllAvailabilitiesHolder = styled.main` + display: flex; + width: 28.75%; + flex-direction: column; +`; + +export const TitleContainer = styled.main` + display: flex; + margin-top: 4.43rem; + margin-bottom: 2.62rem; + justify-content: space-between; + align-items: center; + width: 100%; +`; + +export const YearText = styled(H6)` + margin-bottom: 1.5rem; +`; + +export const AddImage = styled(NextImage)` + width: 1.5rem; + height: 1.5rem; +`; + +export const message = styled(P)` + text-align: center; + margin-top: 5.75rem; +`; diff --git a/app/onboarding/finalize/styles.ts b/app/onboarding/finalize/styles.ts index 759e7ad..34635a6 100644 --- a/app/onboarding/finalize/styles.ts +++ b/app/onboarding/finalize/styles.ts @@ -64,8 +64,13 @@ export const ContinueButton = styled.button` justify-content: center; align-items: center; align-self: stretch; +<<<<<<< HEAD:app/onboarding/yay/styles.ts + border-radius: 99999px; + background: ${COLORS.pomegranate12}; +======= border-radius: 8px; - background: ${COLORS.pomegranate}; + background: ${COLORS.pomegranate12}; +>>>>>>> c33d5a0fb4596e1d2e004014b99e33e17cee93e9:app/onboarding/finalize/styles.ts border-style: solid; border-color: ${COLORS.gray12}; cursor: pointer; diff --git a/app/onboarding/styles.ts b/app/onboarding/styles.ts index 2eefc8b..659830e 100644 --- a/app/onboarding/styles.ts +++ b/app/onboarding/styles.ts @@ -150,9 +150,9 @@ export const Button = styled.button<{ disabled?: boolean }>` width: 30%; height: 2.75rem; background-color: ${({ disabled }) => - disabled ? COLORS.pomegranate10 : COLORS.pomegranate}; + disabled ? COLORS.pomegranate10 : COLORS.pomegranate12}; border-color: ${({ disabled }) => - disabled ? COLORS.pomegranate10 : COLORS.pomegranate}; + disabled ? COLORS.pomegranate10 : COLORS.pomegranate12}; border-style: solid; border-radius: 8px; display: inline-flex; diff --git a/components/AvailabilityCard/AvailabilityCard.tsx b/components/AvailabilityCard/AvailabilityCard.tsx new file mode 100644 index 0000000..c13da6c --- /dev/null +++ b/components/AvailabilityCard/AvailabilityCard.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import Arrow from '@/public/images/white_back.svg'; +import COLORS from '@/styles/colors'; +import { H5, P } from '@/styles/text'; +import { Availabilities, AvailableDates } from '@/types/schema'; +import * as styles from './styles'; + +interface AvailabilityCardProps { + availability: Availabilities; + availableDates: AvailableDates[]; +} + +export default function AvailabilityCard({ + availability, + availableDates, +}: AvailabilityCardProps) { + const dateRange = + availableDates.length > 0 + ? { + earliest: new Date( + Math.min( + ...availableDates.map(date => + new Date(date.available_date).getTime(), + ), + ), + ), + latest: new Date( + Math.max( + ...availableDates.map(date => + new Date(date.available_date).getTime(), + ), + ), + ), + } + : null; + + // Format the dates + let formattedRange = dateRange + ? `${dateRange.earliest.toLocaleString('default', { + month: 'short', + day: 'numeric', + })} - ${dateRange.latest.toLocaleString('default', { + month: 'short', + day: 'numeric', + })}` + : 'No dates available'; + + if (dateRange && dateRange.earliest.getDate() == dateRange.latest.getDate()) { + formattedRange = `${dateRange.earliest.toLocaleString('default', { + month: 'short', + day: 'numeric', + })}`; + } + + return ( + + + +
+ {availability.name} +
+

+ {formattedRange} +

+
+ +
+ +
+ +

+ Description +

+ + {availability.additional_info} + +
+
+
+
+ ); +} diff --git a/components/AvailabilityCard/styles.ts b/components/AvailabilityCard/styles.ts new file mode 100644 index 0000000..be08d1f --- /dev/null +++ b/components/AvailabilityCard/styles.ts @@ -0,0 +1,65 @@ +'use client'; + +import NextImage from 'next/image'; +import styled from 'styled-components'; +import COLORS from '@/styles/colors'; +import { SMALL } from '@/styles/text'; + +export const AvailabilityContainer = styled.main` + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 1.5rem; + border-radius: 16px; + width: 100%; + background: ${COLORS.bread1}; + margin-bottom: 3rem; + box-shadow: 0px 6px 15px -2px rgba(0, 0, 0, 0.08); +`; + +export const AvailabilityHeader = styled.main` + display: flex; + padding: 16px 24px; + justify-content: space-between; + align-items: center; + background: ${COLORS.pomegranate11}; + border-radius: 16px 16px 0 0; + width: 100%; +`; + +export const AvailabilityTitle = styled.main` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; +`; + +export const Content = styled.main` + padding: 0px 24px 24px 24px; + display: flex; + flex-direction: column; + gap: 1.5rem; +`; + +export const SubHeader = styled.main` + display: flex; + flex-direction: column; + gap: 4px; + justify-content: start; + margin-bottom: 0.25rem; +`; + +export const Arrow = styled(NextImage)` + width: 24px; + height: 24px; + transform: rotate(180deg); +`; + +export const TruncatedText = styled(SMALL)` + display: -webkit-box; + -webkit-line-clamp: 2; /* Limit to 2 lines */ + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + white-space: normal; +`; diff --git a/components/MenuBar/MenuBar.tsx b/components/MenuBar/MenuBar.tsx index 44813d0..aad6b6e 100644 --- a/components/MenuBar/MenuBar.tsx +++ b/components/MenuBar/MenuBar.tsx @@ -12,14 +12,19 @@ import { ToggleButton, } from './styles'; -const MenuBar: React.FC = () => { +const MenuBar: React.FC<{ setMenuExpanded?: (expanded: boolean) => void }> = ({ + setMenuExpanded = () => {}, // Default to a no-op function +}) => { const [expanded, setExpanded] = useState(false); const [activeItem, setActiveItem] = useState(null); const router = useRouter(); - const toggleMenu = () => setExpanded(!expanded); + const toggleMenu = () => { + const newExpanded = !expanded; + setExpanded(newExpanded); + setMenuExpanded(newExpanded); // Update parent component about expanded state + }; - // TODO: add navigation by passing in path prop const handleClick = (item: string, path: string) => { setActiveItem(item); router.push(path); diff --git a/components/MenuBar/styles.ts b/components/MenuBar/styles.ts index cf8e665..173e705 100644 --- a/components/MenuBar/styles.ts +++ b/components/MenuBar/styles.ts @@ -8,7 +8,7 @@ export const MenuContainer = styled.div<{ $expanded: boolean }>` height: 100vh; z-index: 9999; background-color: ${({ $expanded }) => - $expanded ? COLORS.pomegranate : 'transparent'}; + $expanded ? COLORS.pomegranate12 : 'transparent'}; display: flex; flex-direction: column; padding-left: 1.25rem; @@ -47,7 +47,8 @@ export const MenuIconWrapper = styled.div<{ $expanded: boolean }>` width: 20px; height: 20px; & svg path { - fill: ${({ $expanded }) => ($expanded ? COLORS.gray3 : COLORS.pomegranate)}; + fill: ${({ $expanded }) => + $expanded ? COLORS.gray3 : COLORS.pomegranate12}; } `; diff --git a/public/images/add.svg b/public/images/add.svg new file mode 100644 index 0000000..8aa80d3 --- /dev/null +++ b/public/images/add.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/additionalinfo.svg b/public/images/additionalinfo.svg new file mode 100644 index 0000000..0811a52 --- /dev/null +++ b/public/images/additionalinfo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/clock.svg b/public/images/clock.svg new file mode 100644 index 0000000..e911704 --- /dev/null +++ b/public/images/clock.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/styles/colors.ts b/styles/colors.ts index 1f35755..0f9b245 100644 --- a/styles/colors.ts +++ b/styles/colors.ts @@ -51,8 +51,9 @@ const COLORS = { lilac11: '#423EAF', lilac12: '#383975', - pomegranate: '#342A2F', pomegranate10: '#6F585E', + pomegranate11: '#633A4F', + pomegranate12: '#342A2F', }; export default COLORS; diff --git a/types/schema.d.ts b/types/schema.d.ts index 6e6c04e..533adb6 100644 --- a/types/schema.d.ts +++ b/types/schema.d.ts @@ -76,6 +76,8 @@ export interface AvailableDates { date_id: UUID; availability_id: UUID; available_date: string; //date + test_col: string; //timestamptz + test_col2: string; //timestamptz } export interface Event {