diff --git a/package-lock.json b/package-lock.json index 04dd443..d7d260c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,6 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^0.2.1", - "date-fns": "^3.3.1", "lucide-react": "^0.314.0", "next": "^14.2.3", "next-themes": "^0.2.1", @@ -2756,6 +2755,8 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" diff --git a/package.json b/package.json index 69b79a2..c539ea3 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^0.2.1", - "date-fns": "^3.3.1", "lucide-react": "^0.314.0", "next": "^14.2.3", "next-themes": "^0.2.1", diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index ed980da..7b4c425 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -60,6 +60,7 @@ import { ActionsList } from "@/components/ui/coaching-sessions/actions-list"; import { Action } from "@/types/action"; import { createAction, deleteAction, updateAction } from "@/lib/api/actions"; import { DateTime } from "ts-luxon"; +import { CoachingSessionTitle } from "@/components/ui/coaching-sessions/coaching-session-title"; // export const metadata: Metadata = { // title: "Coaching Session", @@ -72,18 +73,20 @@ export default function CoachingSessionsPage() { const [note, setNote] = useState(""); const [syncStatus, setSyncStatus] = useState(""); const { userId } = useAuthStore((state) => state); - const { coachingSessionId } = useAppStateStore((state) => state); + const { coachingSession, coachingRelationship } = useAppStateStore( + (state) => state + ); useEffect(() => { async function fetchNote() { - if (!coachingSessionId) { + if (!coachingSession.id) { console.error( - "Failed to fetch Note since coachingSessionId is not set." + "Failed to fetch Note since coachingSession.id is not set." ); return; } - await fetchNotesByCoachingSessionId(coachingSessionId) + await fetchNotesByCoachingSessionId(coachingSession.id) .then((notes) => { const note = notes[0]; if (notes.length > 0) { @@ -91,7 +94,7 @@ export default function CoachingSessionsPage() { setNoteId(note.id); setNote(note.body); } else { - console.trace("No Notes associated with this coachingSessionId"); + console.trace("No Notes associated with this coachingSession.id"); } }) .catch((err) => { @@ -101,11 +104,11 @@ export default function CoachingSessionsPage() { }); } fetchNote(); - }, [coachingSessionId, noteId]); + }, [coachingSession.id, noteId]); const handleAgreementAdded = (body: string): Promise => { // Calls the backend endpoint that creates and stores a full Agreement entity - return createAgreement(coachingSessionId, userId, body) + return createAgreement(coachingSession.id, userId, body) .then((agreement) => { return agreement; }) @@ -116,7 +119,7 @@ export default function CoachingSessionsPage() { }; const handleAgreementEdited = (id: Id, body: string): Promise => { - return updateAgreement(id, coachingSessionId, userId, body) + return updateAgreement(id, coachingSession.id, userId, body) .then((agreement) => { return agreement; }) @@ -143,7 +146,7 @@ export default function CoachingSessionsPage() { dueBy: DateTime ): Promise => { // Calls the backend endpoint that creates and stores a full Action entity - return createAction(coachingSessionId, body, status, dueBy) + return createAction(coachingSession.id, body, status, dueBy) .then((action) => { return action; }) @@ -159,7 +162,7 @@ export default function CoachingSessionsPage() { status: ActionStatus, dueBy: DateTime ): Promise => { - return updateAction(id, coachingSessionId, body, status, dueBy) + return updateAction(id, coachingSession.id, body, status, dueBy) .then((action) => { return action; }) @@ -183,8 +186,8 @@ export default function CoachingSessionsPage() { const handleInputChange = (value: string) => { setNote(value); - if (noteId && coachingSessionId && userId) { - updateNote(noteId, coachingSessionId, userId, value) + if (noteId && coachingSession.id && userId) { + updateNote(noteId, coachingSession.id, userId, value) .then((note) => { console.trace("Updated Note: " + noteToString(note)); setSyncStatus("All changes saved"); @@ -193,8 +196,8 @@ export default function CoachingSessionsPage() { setSyncStatus("Failed to save changes"); console.error("Failed to update Note: " + err); }); - } else if (!noteId && coachingSessionId && userId) { - createNote(coachingSessionId, userId, value) + } else if (!noteId && coachingSession.id && userId) { + createNote(coachingSession.id, userId, value) .then((note) => { console.trace("Newly created Note: " + noteToString(note)); setNoteId(note.id); @@ -206,7 +209,7 @@ export default function CoachingSessionsPage() { }); } else { console.error( - "Could not update or create a Note since coachingSessionId or userId are not set." + "Could not update or create a Note since coachingSession.id or userId are not set." ); } }; @@ -215,11 +218,19 @@ export default function CoachingSessionsPage() { setSyncStatus(""); }; + const handleTitleRender = (sessionTitle: string) => { + document.title = sessionTitle; + }; + return ( <> -
+
-

Session Title

+
@@ -289,7 +300,7 @@ export default function CoachingSessionsPage() {
{ async function loadActions() { - if (!coachingSessionId) return; + if (!coachingSessionId) { + console.error( + "Failed to fetch Actions since coachingSession.id is not set." + ); + return; + } await fetchActionsByCoachingSessionId(coachingSessionId) .then((actions) => { diff --git a/src/components/ui/coaching-sessions/agreements-list.tsx b/src/components/ui/coaching-sessions/agreements-list.tsx index 97051b1..6f16481 100644 --- a/src/components/ui/coaching-sessions/agreements-list.tsx +++ b/src/components/ui/coaching-sessions/agreements-list.tsx @@ -19,12 +19,7 @@ import { } from "@/components/ui/dropdown-menu"; import { MoreHorizontal, ArrowUpDown, Save } from "lucide-react"; import { Id } from "@/types/general"; -import { - createAgreement, - deleteAgreement as deleteAgreementApi, - updateAgreement as updateAgreementApi, - fetchAgreementsByCoachingSessionId, -} from "@/lib/api/agreements"; +import { fetchAgreementsByCoachingSessionId } from "@/lib/api/agreements"; import { Agreement, agreementToString } from "@/types/agreement"; import { DateTime } from "ts-luxon"; import { siteConfig } from "@/site.config"; @@ -158,7 +153,12 @@ const AgreementsList: React.FC<{ useEffect(() => { async function loadAgreements() { - if (!coachingSessionId) return; + if (!coachingSessionId) { + console.error( + "Failed to fetch Agreements since coachingSession.id is not set." + ); + return; + } await fetchAgreementsByCoachingSessionId(coachingSessionId) .then((agreements) => { diff --git a/src/components/ui/coaching-sessions/coaching-session-title.tsx b/src/components/ui/coaching-sessions/coaching-session-title.tsx new file mode 100644 index 0000000..c88f11b --- /dev/null +++ b/src/components/ui/coaching-sessions/coaching-session-title.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + defaultSessionTitle, + generateSessionTitle, + SessionTitle, + SessionTitleStyle, +} from "@/types/session-title"; +import { + CoachingRelationshipWithUserNames, + coachingRelationshipWithUserNamesToString, +} from "@/types/coaching_relationship_with_user_names"; +import { + CoachingSession, + coachingSessionToString, +} from "@/types/coaching-session"; +import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; + +const CoachingSessionTitle: React.FC<{ + locale: string | "us"; + style: SessionTitleStyle; + onRender: (sessionTitle: string) => void; +}> = ({ locale, style, onRender }) => { + const [isLoading, setIsLoading] = useState(true); + const [sessionTitle, setSessionTitle] = useState(); + const { coachingSession, coachingRelationship } = useAppStateStore( + (state) => state + ); + useState(); + + useEffect(() => { + if (coachingSession && coachingRelationship) { + setIsLoading(false); + const title = generateSessionTitle( + coachingSession, + coachingRelationship, + style, + locale + ); + setSessionTitle(title); + onRender(title.title); + } + }, [coachingSession, coachingRelationship, style, locale, onRender]); + + if (isLoading) { + return ( +

+ {defaultSessionTitle().title} +

+ ); + } + + return ( +

+ {sessionTitle ? sessionTitle.title : defaultSessionTitle().title} +

+ ); +}; + +export { CoachingSessionTitle }; diff --git a/src/components/ui/dashboard/select-coaching-session.tsx b/src/components/ui/dashboard/select-coaching-session.tsx index 6b13dd3..4ad7a43 100644 --- a/src/components/ui/dashboard/select-coaching-session.tsx +++ b/src/components/ui/dashboard/select-coaching-session.tsx @@ -23,9 +23,17 @@ import { fetchCoachingRelationshipsWithUserNames } from "@/lib/api/coaching-rela import { fetchCoachingSessions } from "@/lib/api/coaching-sessions"; import { fetchOrganizationsByUserId } from "@/lib/api/organizations"; import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; -import { CoachingSession } from "@/types/coaching-session"; -import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names"; -import { Id } from "@/types/general"; +import { + CoachingSession, + coachingSessionToString, + getCoachingSessionById, +} from "@/types/coaching-session"; +import { + CoachingRelationshipWithUserNames, + coachingRelationshipWithUserNamesToString, + getCoachingRelationshipById, +} from "@/types/coaching_relationship_with_user_names"; +import { getDateTimeFromString, Id } from "@/types/general"; import { Organization } from "@/types/organization"; import Link from "next/link"; import { useEffect, useState } from "react"; @@ -46,9 +54,15 @@ export function SelectCoachingSession({ const { relationshipId, setRelationshipId } = useAppStateStore( (state) => state ); + const { coachingRelationship, setCoachingRelationship } = useAppStateStore( + (state) => state + ); const { coachingSessionId, setCoachingSessionId } = useAppStateStore( (state) => state ); + const { coachingSession, setCoachingSession } = useAppStateStore( + (state) => state + ); const [organizations, setOrganizations] = useState([]); const [coachingRelationships, setCoachingRelationships] = useState< @@ -115,6 +129,31 @@ export function SelectCoachingSession({ loadCoachingSessions(); }, [relationshipId]); + const handleSetCoachingRelationship = (coachingRelationshipId: string) => { + setRelationshipId(coachingRelationshipId); + const coachingRelationship = getCoachingRelationshipById( + coachingRelationshipId, + coachingRelationships + ); + console.debug( + "coachingRelationship: " + + coachingRelationshipWithUserNamesToString(coachingRelationship) + ); + setCoachingRelationship(coachingRelationship); + }; + + const handleSetCoachingSession = (coachingSessionId: string) => { + setCoachingSessionId(coachingSessionId); + const coachingSession = getCoachingSessionById( + coachingSessionId, + coachingSessions + ); + console.debug( + "coachingSession: " + coachingSessionToString(coachingSession) + ); + setCoachingSession(coachingSession); + }; + return ( @@ -154,7 +193,7 @@ export function SelectCoachingSession({ defaultValue="caleb" disabled={!organizationId} value={relationshipId} - onValueChange={setRelationshipId} + onValueChange={handleSetCoachingRelationship} > @@ -181,36 +220,48 @@ export function SelectCoachingSession({ defaultValue="today" disabled={!relationshipId} value={coachingSessionId} - onValueChange={setCoachingSessionId} + onValueChange={handleSetCoachingSession} > {coachingSessions.some( - (session) => session.date < DateTime.now() + (session) => + getDateTimeFromString(session.date) < DateTime.now() ) && ( Previous Sessions {coachingSessions - .filter((session) => session.date < DateTime.now()) + .filter( + (session) => + getDateTimeFromString(session.date) < DateTime.now() + ) .map((session) => ( - {session.date.toLocaleString(DateTime.DATETIME_FULL)} + {getDateTimeFromString(session.date).toLocaleString( + DateTime.DATETIME_FULL + )} ))} )} {coachingSessions.some( - (session) => session.date >= DateTime.now() + (session) => + getDateTimeFromString(session.date) >= DateTime.now() ) && ( Upcoming Sessions {coachingSessions - .filter((session) => session.date >= DateTime.now()) + .filter( + (session) => + getDateTimeFromString(session.date) >= DateTime.now() + ) .map((session) => ( - {session.date.toLocaleString(DateTime.DATETIME_FULL)} + {getDateTimeFromString(session.date).toLocaleString( + DateTime.DATETIME_FULL + )} ))} diff --git a/src/lib/api/coaching-relationships.ts b/src/lib/api/coaching-relationships.ts index d60945e..1e2f89a 100644 --- a/src/lib/api/coaching-relationships.ts +++ b/src/lib/api/coaching-relationships.ts @@ -3,35 +3,99 @@ import { CoachingRelationshipWithUserNames, coachingRelationshipsWithUserNamesToString, + defaultCoachingRelationshipWithUserNames, defaultCoachingRelationshipsWithUserNames, - isCoachingRelationshipWithUserNamesArray + isCoachingRelationshipWithUserNames, + isCoachingRelationshipWithUserNamesArray, + parseCoachingRelationshipWithUserNames, } from "@/types/coaching_relationship_with_user_names"; import { Id } from "@/types/general"; import { AxiosError, AxiosResponse } from "axios"; +export const fetchCoachingRelationshipWithUserNames = async ( + organization_id: Id, + relationship_id: Id +): Promise => { + const axios = require("axios"); + + var relationship: CoachingRelationshipWithUserNames = + defaultCoachingRelationshipWithUserNames(); + var err: string = ""; + + const data = await axios + .get( + `http://localhost:4000/organizations/${organization_id}/coaching_relationships/${relationship_id}`, + { + withCredentials: true, + setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend + headers: { + "X-Version": "0.0.1", + }, + } + ) + .then(function (response: AxiosResponse) { + // handle success + const relationshipData = response.data.data; + if (isCoachingRelationshipWithUserNames(relationshipData)) { + relationship = parseCoachingRelationshipWithUserNames(relationshipData); + } + }) + .catch(function (error: AxiosError) { + // handle error + console.error(error.response?.status); + if (error.response?.status == 401) { + err = + "Retrieval of CoachingRelationshipWithUserNames failed: unauthorized."; + } else if (error.response?.status == 500) { + err = + "Retrieval of CoachingRelationshipWithUserNames failed, system error: " + + error.response.data; + } else { + err = + `Retrieval of CoachingRelationshipWithUserNames(` + + relationship_id + + `) failed: ` + + error.response?.data; + } + }); + + if (err) { + console.error(err); + throw err; + } + + return relationship; +}; + export const fetchCoachingRelationshipsWithUserNames = async ( organizationId: Id ): Promise<[CoachingRelationshipWithUserNames[], string]> => { const axios = require("axios"); - var relationships: CoachingRelationshipWithUserNames[] = defaultCoachingRelationshipsWithUserNames(); + var relationships: CoachingRelationshipWithUserNames[] = + defaultCoachingRelationshipsWithUserNames(); var err: string = ""; const data = await axios - .get(`http://localhost:4000/organizations/${organizationId}/coaching_relationships`, { - withCredentials: true, - setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend - headers: { - "X-Version": "0.0.1", - }, - }) + .get( + `http://localhost:4000/organizations/${organizationId}/coaching_relationships`, + { + withCredentials: true, + setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend + headers: { + "X-Version": "0.0.1", + }, + } + ) .then(function (response: AxiosResponse) { // handle success console.debug(response); if (isCoachingRelationshipWithUserNamesArray(response.data.data)) { relationships = response.data.data; console.debug( - `CoachingRelationshipsWithUserNames: ` + coachingRelationshipsWithUserNamesToString(relationships) + `.` + `CoachingRelationshipsWithUserNames: ` + + coachingRelationshipsWithUserNamesToString(relationships) + + `.` ); } }) @@ -39,17 +103,24 @@ export const fetchCoachingRelationshipsWithUserNames = async ( // handle error console.error(error.response?.status); if (error.response?.status == 401) { - console.error("Retrieval of CoachingRelationshipsWithUserNames failed: unauthorized."); - err = "Retrieval of CoachingRelationshipsWithUserNames failed: unauthorized."; + console.error( + "Retrieval of CoachingRelationshipsWithUserNames failed: unauthorized." + ); + err = + "Retrieval of CoachingRelationshipsWithUserNames failed: unauthorized."; } else { console.log(error); console.error( - `Retrieval of CoachingRelationshipsWithUserNames by organization Id (` + organizationId + `) failed.` + `Retrieval of CoachingRelationshipsWithUserNames by organization Id (` + + organizationId + + `) failed.` ); err = - `Retrieval of CoachingRealtionshipsWithUserNames by organization Id (` + organizationId + `) failed.`; + `Retrieval of CoachingRealtionshipsWithUserNames by organization Id (` + + organizationId + + `) failed.`; } }); return [relationships, err]; -}; \ No newline at end of file +}; diff --git a/src/lib/api/coaching-sessions.ts b/src/lib/api/coaching-sessions.ts index d260d8c..6d68490 100644 --- a/src/lib/api/coaching-sessions.ts +++ b/src/lib/api/coaching-sessions.ts @@ -4,7 +4,7 @@ import { CoachingSession, isCoachingSessionArray, parseCoachingSession, - sortCoachingSessionArray + sortCoachingSessionArray, } from "@/types/coaching-session"; import { Id, SortOrder } from "@/types/general"; import { AxiosError, AxiosResponse } from "axios"; @@ -21,8 +21,8 @@ export const fetchCoachingSessions = async ( // TODO: for now we hardcode a 2 month window centered around now, // eventually we want to make this be configurable somewhere // (either on the page or elsewhere) - const fromDate = DateTime.now().minus({month: 1}).toISODate(); - const toDate = DateTime.now().plus({month: 1}).toISODate(); + const fromDate = DateTime.now().minus({ month: 1 }).toISODate(); + const toDate = DateTime.now().plus({ month: 1 }).toISODate(); console.debug("fromDate: " + fromDate); console.debug("toDate: " + toDate); @@ -32,7 +32,7 @@ export const fetchCoachingSessions = async ( params: { coaching_relationship_id: coachingRelationshipId, from_date: fromDate, - to_date: toDate + to_date: toDate, }, withCredentials: true, setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend @@ -45,10 +45,13 @@ export const fetchCoachingSessions = async ( var sessions_data = response.data.data; if (isCoachingSessionArray(sessions_data)) { // Sort returned sessions in ascending order by their date field - sessions_data = sortCoachingSessionArray(sessions_data, SortOrder.Ascending); + sessions_data = sortCoachingSessionArray( + sessions_data, + SortOrder.Ascending + ); sessions_data.forEach((session_data: any) => { - coaching_sessions.push(parseCoachingSession(session_data)) + coaching_sessions.push(parseCoachingSession(session_data)); }); } }) @@ -61,10 +64,14 @@ export const fetchCoachingSessions = async ( } else { console.log(error); console.error( - `Retrieval of CoachingSessions by coaching relationship Id (` + coachingRelationshipId + `) failed.` + `Retrieval of CoachingSessions by coaching relationship Id (` + + coachingRelationshipId + + `) failed.` ); err = - `Retrieval of CoachingSessions by coaching relationship Id (` + coachingRelationshipId + `) failed.`; + `Retrieval of CoachingSessions by coaching relationship Id (` + + coachingRelationshipId + + `) failed.`; } }); diff --git a/src/lib/stores/app-state-store.ts b/src/lib/stores/app-state-store.ts index 6715907..88088cb 100644 --- a/src/lib/stores/app-state-store.ts +++ b/src/lib/stores/app-state-store.ts @@ -1,17 +1,23 @@ +import { CoachingSession, defaultCoachingSession } from '@/types/coaching-session'; +import { CoachingRelationshipWithUserNames, defaultCoachingRelationshipWithUserNames } from '@/types/coaching_relationship_with_user_names'; import { Id } from '@/types/general'; -import { create, useStore } from 'zustand'; +import { create } from 'zustand'; import { createJSONStorage, devtools, persist } from 'zustand/middleware'; interface AppState { organizationId: Id; relationshipId: Id; coachingSessionId: Id; + coachingSession: CoachingSession; + coachingRelationship: CoachingRelationshipWithUserNames; } interface AppStateActions { setOrganizationId: (organizationId: Id) => void; setRelationshipId: (relationshipId: Id) => void; setCoachingSessionId: (coachingSessionId: Id) => void; + setCoachingSession: (coachingSession: CoachingSession) => void; + setCoachingRelationship: (coachingRelationship: CoachingRelationshipWithUserNames) => void; reset (): void; } @@ -21,6 +27,8 @@ export const defaultInitState: AppState = { organizationId: "", relationshipId: "", coachingSessionId: "", + coachingSession: defaultCoachingSession(), + coachingRelationship: defaultCoachingRelationshipWithUserNames(), } export const createAppStateStore = ( @@ -41,6 +49,12 @@ export const createAppStateStore = ( setCoachingSessionId: (coachingSessionId) => { set({ coachingSessionId }); }, + setCoachingSession: (coachingSession) => { + set({ coachingSession }); + }, + setCoachingRelationship: (coachingRelationship) => { + set({ coachingRelationship }); + }, reset (): void { set(defaultInitState); } diff --git a/src/site.config.ts b/src/site.config.ts index be3c703..79d560e 100644 --- a/src/site.config.ts +++ b/src/site.config.ts @@ -1,23 +1,24 @@ export const siteConfig = { - name: "Refactor Coaching & Mentoring", - url: "https://refactorcoach.com", - ogImage: "https://ui.shadcn.com/og.jpg", - locale: "us", - description: - "A platform for software engineers and tech leaders to level up their foundational skills.", - links: { - twitter: "https://twitter.com/shadcn", - github: "https://github.com/shadcn-ui/ui", - }, - } - - export type SiteConfig = typeof siteConfig + name: "Refactor Coaching & Mentoring", + url: "https://refactorcoach.com", + ogImage: "https://ui.shadcn.com/og.jpg", + locale: "us", + titleStyle: SessionTitleStyle.CoachFirstCoacheeFirstDate, + description: "Coaching and mentorship done right.", + links: { + twitter: "https://twitter.com/shadcn", + github: "https://github.com/shadcn-ui/ui", + }, +}; + +export type SiteConfig = typeof siteConfig; -import { MainNavItem, SidebarNavItem } from "./types/nav" +import { MainNavItem, SidebarNavItem } from "./types/nav"; +import { SessionTitleStyle } from "./types/session-title"; interface DocsConfig { - mainNav: MainNavItem[] - sidebarNav: SidebarNavItem[] + mainNav: MainNavItem[]; + sidebarNav: SidebarNavItem[]; } export const docsConfig: DocsConfig = { @@ -345,4 +346,4 @@ export const docsConfig: DocsConfig = { ], }, ], -} \ No newline at end of file +}; diff --git a/src/types/coaching-session.ts b/src/types/coaching-session.ts index 47e53ab..03c5111 100644 --- a/src/types/coaching-session.ts +++ b/src/types/coaching-session.ts @@ -5,9 +5,9 @@ import { Id, SortOrder } from "@/types/general"; // entity::coaching_sessions::Model export interface CoachingSession { id: Id; - coaching_relationship_id: Id, - date: DateTime, - timezone: String, + coaching_relationship_id: Id; + date: string; + timezone: string; created_at: DateTime; updated_at: DateTime; } @@ -17,12 +17,12 @@ export interface CoachingSession { // will agree to work with internally. export function parseCoachingSession(data: any): CoachingSession { if (!isCoachingSession(data)) { - throw new Error('Invalid CoachingSession data'); + throw new Error("Invalid CoachingSession data"); } return { id: data.id, coaching_relationship_id: data.coaching_relationship_id, - date: DateTime.fromISO(data.date.toString()), + date: data.date, timezone: data.timezone, created_at: DateTime.fromISO(data.created_at.toString()), updated_at: DateTime.fromISO(data.updated_at.toString()), @@ -30,56 +30,79 @@ export function parseCoachingSession(data: any): CoachingSession { } export function isCoachingSession(value: unknown): value is CoachingSession { - if (!value || typeof value !== "object") { - return false; - } - const object = value as Record; - - return ( - typeof object.id === "string" && - typeof object.coaching_relationship_id === "string" && - typeof object.date === "string" && - typeof object.timezone === "string" && - typeof object.created_at === "string" && - typeof object.updated_at === "string" - ); + if (!value || typeof value !== "object") { + return false; } + const object = value as Record; + + return ( + typeof object.id === "string" && + typeof object.coaching_relationship_id === "string" && + typeof object.date === "string" && + typeof object.timezone === "string" && + typeof object.created_at === "string" && + typeof object.updated_at === "string" + ); +} -export function isCoachingSessionArray(value: unknown): value is CoachingSession[] { +export function isCoachingSessionArray( + value: unknown +): value is CoachingSession[] { return Array.isArray(value) && value.every(isCoachingSession); } -export function sortCoachingSessionArray(sessions: CoachingSession[], order: SortOrder): CoachingSession[] { +export function sortCoachingSessionArray( + sessions: CoachingSession[], + order: SortOrder +): CoachingSession[] { if (order == SortOrder.Ascending) { - sessions.sort((a, b) => - new Date(a.date.toString()).getTime() - new Date(b.date.toString()).getTime()); + sessions.sort( + (a, b) => + new Date(a.date.toString()).getTime() - + new Date(b.date.toString()).getTime() + ); } else if (order == SortOrder.Descending) { - sessions.sort((a, b) => - new Date(b.date.toString()).getTime() - new Date(a.date.toString()).getTime()); + sessions.sort( + (a, b) => + new Date(b.date.toString()).getTime() - + new Date(a.date.toString()).getTime() + ); } return sessions; } +export function getCoachingSessionById( + id: string, + sessions: CoachingSession[] +): CoachingSession { + const session = sessions.find((session) => session.id === id); + return session ? session : defaultCoachingSession(); +} + export function defaultCoachingSession(): CoachingSession { - var now = DateTime.now(); - return { - id: "", - coaching_relationship_id: "", - date: now, - timezone: "", - created_at: now, - updated_at: now, - }; - } - - export function defaultCoachingSessions(): CoachingSession[] { - return [defaultCoachingSession()]; - } - - export function coachingSessionToString(coaching_session: CoachingSession): string { - return JSON.stringify(coaching_session); - } - - export function coachingSessionsToString(coaching_sessions: CoachingSession[]): string { - return JSON.stringify(coaching_sessions); - } \ No newline at end of file + var now = DateTime.now(); + return { + id: "", + coaching_relationship_id: "", + date: "", + timezone: "", + created_at: now, + updated_at: now, + }; +} + +export function defaultCoachingSessions(): CoachingSession[] { + return [defaultCoachingSession()]; +} + +export function coachingSessionToString( + coaching_session: CoachingSession | undefined +): string { + return JSON.stringify(coaching_session); +} + +export function coachingSessionsToString( + coaching_sessions: CoachingSession[] | undefined +): string { + return JSON.stringify(coaching_sessions); +} diff --git a/src/types/coaching_relationship_with_user_names.ts b/src/types/coaching_relationship_with_user_names.ts index cd5f619..620fc59 100644 --- a/src/types/coaching_relationship_with_user_names.ts +++ b/src/types/coaching_relationship_with_user_names.ts @@ -7,60 +7,104 @@ export interface CoachingRelationshipWithUserNames { id: Id; coach_id: Id; coachee_id: Id; - coach_first_name: String; - coach_last_name: String; - coachee_first_name: String; - coachee_last_name: String; + coach_first_name: string; + coach_last_name: string; + coachee_first_name: string; + coachee_last_name: string; created_at: DateTime; updated_at: DateTime; } -export function isCoachingRelationshipWithUserNames(value: unknown): value is CoachingRelationshipWithUserNames { - if (!value || typeof value !== "object") { - return false; - } - const object = value as Record; - - return ( - typeof object.id === "string" && - typeof object.coach_id === "string" && - typeof object.coachee_id === "string" && - typeof object.coach_first_name === "string" && - typeof object.coach_last_name === "string" && - typeof object.coachee_first_name === "string" && - typeof object.coachee_last_name === "string" && - typeof object.created_at === "string" && - typeof object.updated_at === "string" - ); +// The main purpose of having this parsing function is to be able to parse the +// returned DateTimeWithTimeZone (Rust type) string into something that ts-luxon +// will agree to work with internally. +export function parseCoachingRelationshipWithUserNames( + data: any +): CoachingRelationshipWithUserNames { + if (!isCoachingRelationshipWithUserNames(data)) { + throw new Error("Invalid CoachingRelationshipWithUserNames object data"); } + return { + id: data.id, + coach_id: data.coach_id, + coachee_id: data.coachee_id, + coach_first_name: data.coach_first_name, + coach_last_name: data.coach_last_name, + coachee_first_name: data.coachee_first_name, + coachee_last_name: data.coachee_last_name, + created_at: DateTime.fromISO(data.created_at.toString()), + updated_at: DateTime.fromISO(data.updated_at.toString()), + }; +} + +export function isCoachingRelationshipWithUserNames( + value: unknown +): value is CoachingRelationshipWithUserNames { + if (!value || typeof value !== "object") { + return false; + } + const object = value as Record; + + return ( + typeof object.id === "string" && + typeof object.coach_id === "string" && + typeof object.coachee_id === "string" && + typeof object.coach_first_name === "string" && + typeof object.coach_last_name === "string" && + typeof object.coachee_first_name === "string" && + typeof object.coachee_last_name === "string" && + typeof object.created_at === "string" && + typeof object.updated_at === "string" + ); +} -export function isCoachingRelationshipWithUserNamesArray(value: unknown): value is CoachingRelationshipWithUserNames[] { - return Array.isArray(value) && value.every(isCoachingRelationshipWithUserNames); +export function isCoachingRelationshipWithUserNamesArray( + value: unknown +): value is CoachingRelationshipWithUserNames[] { + return ( + Array.isArray(value) && value.every(isCoachingRelationshipWithUserNames) + ); +} + +export function getCoachingRelationshipById( + id: string, + relationships: CoachingRelationshipWithUserNames[] +): CoachingRelationshipWithUserNames { + const relationship = relationships.find( + (relationship) => relationship.id === id + ); + return relationship + ? relationship + : defaultCoachingRelationshipWithUserNames(); } export function defaultCoachingRelationshipWithUserNames(): CoachingRelationshipWithUserNames { - var now = DateTime.now(); - return { - id: "", - coach_id: "", - coachee_id: "", - coach_first_name: "", - coach_last_name: "", - coachee_first_name: "", - coachee_last_name: "", - created_at: now, - updated_at: now, - }; - } - - export function defaultCoachingRelationshipsWithUserNames(): CoachingRelationshipWithUserNames[] { - return [defaultCoachingRelationshipWithUserNames()]; - } - - export function coachingRelationshipWithUserNamesToString(relationship: CoachingRelationshipWithUserNames): string { - return JSON.stringify(relationship); - } - - export function coachingRelationshipsWithUserNamesToString(relationships: CoachingRelationshipWithUserNames[]): string { - return JSON.stringify(relationships); - } \ No newline at end of file + var now = DateTime.now(); + return { + id: "", + coach_id: "", + coachee_id: "", + coach_first_name: "", + coach_last_name: "", + coachee_first_name: "", + coachee_last_name: "", + created_at: now, + updated_at: now, + }; +} + +export function defaultCoachingRelationshipsWithUserNames(): CoachingRelationshipWithUserNames[] { + return [defaultCoachingRelationshipWithUserNames()]; +} + +export function coachingRelationshipWithUserNamesToString( + relationship: CoachingRelationshipWithUserNames | undefined +): string { + return JSON.stringify(relationship); +} + +export function coachingRelationshipsWithUserNamesToString( + relationships: CoachingRelationshipWithUserNames[] | undefined +): string { + return JSON.stringify(relationships); +} diff --git a/src/types/general.ts b/src/types/general.ts index 6cbb74b..f805ecb 100644 --- a/src/types/general.ts +++ b/src/types/general.ts @@ -1,21 +1,23 @@ +import { DateTime } from "ts-luxon"; + // A type alias for each entity's Id field export type Id = string; // A sorting type that can be used by any of our custom types when stored // as arrays export enum SortOrder { - Ascending = "ascending", - Descending = "descending" - } + Ascending = "ascending", + Descending = "descending", +} export enum ActionStatus { NotStarted = "NotStarted", InProgress = "InProgress", Completed = "Completed", - WontDo = "WontDo" + WontDo = "WontDo", } -export function stringToActionStatus(statusString: string): (ActionStatus) { +export function stringToActionStatus(statusString: string): ActionStatus { const status = statusString.trim(); if (status == "InProgress") { @@ -29,7 +31,7 @@ export function stringToActionStatus(statusString: string): (ActionStatus) { } } -export function actionStatusToString(actionStatus: ActionStatus): (string) { +export function actionStatusToString(actionStatus: ActionStatus): string { if (actionStatus == "InProgress") { return "In Progress"; } else if (actionStatus == "Completed") { @@ -39,4 +41,18 @@ export function actionStatusToString(actionStatus: ActionStatus): (string) { } else { return "Not Started"; } -} \ No newline at end of file +} + +/// Given a valid ISO formatted date time string (timestampz in Postgresql types), +/// return a valid DateTime object instance. +export function getDateTimeFromString(dateTime: string): DateTime { + const dt = dateTime.trim(); + if (dt.length == 0) { + console.warn( + "Return DateTime.now() since input dateTime string was empty." + ); + return DateTime.now(); + } + + return DateTime.fromISO(dt); +} diff --git a/src/types/session-title.ts b/src/types/session-title.ts new file mode 100644 index 0000000..33fca80 --- /dev/null +++ b/src/types/session-title.ts @@ -0,0 +1,129 @@ +import { DateTime } from "ts-luxon"; +import { CoachingRelationshipWithUserNames } from "./coaching_relationship_with_user_names"; +import { CoachingSession } from "./coaching-session"; +import { siteConfig } from "@/site.config"; +import { getDateTimeFromString } from "./general"; + +// This is a frontend type only, it does not reflect any literal entity model +// from the backend. +export interface SessionTitle { + title: string; + style: SessionTitleStyle; +} + +// Different title rendering styles +export enum SessionTitleStyle { + CoachFirstCoacheeFirst, + CoachFirstLastCoacheeFirstLast, + CoachFirstCoacheeFirstDate, + CoachFirstCoacheeFirstDateTime, +} + +function constructCoachFirstCoacheeFirstTitle( + coach_first_name: string, + coachee_first_name: string +): string { + return coach_first_name + " <> " + coachee_first_name; +} + +function constructCoachFirstLastCoacheeFirstLastTitle( + coach_first_name: string, + coach_last_name: string, + coachee_first_name: string, + coachee_last_name: string +): string { + return ( + coach_first_name + + " " + + coach_last_name + + " <> " + + coachee_first_name + + " " + + coachee_last_name + ); +} + +function constructCoachFirstCoacheeFirstDate( + coach_first_name: string, + coachee_first_name: string, + date: string, + locale: string +): string { + var title = coach_first_name + " <> " + coachee_first_name; + var formattedDateTime = + " @ " + + getDateTimeFromString(date) + .setLocale(locale) + .toLocaleString(DateTime.DATE_MED); + + return coach_first_name + " <> " + coachee_first_name + formattedDateTime; +} + +function constructCoachFirstCoacheeFirstDateTime( + coach_first_name: string, + coachee_first_name: string, + date: string, + locale: string +): string { + var title = coach_first_name + " <> " + coachee_first_name; + var formattedDateTime = + " @ " + + getDateTimeFromString(date) + .setLocale(locale) + .toLocaleString(DateTime.DATETIME_MED); + + return coach_first_name + " <> " + coachee_first_name + formattedDateTime; +} + +export function generateSessionTitle( + session: CoachingSession, + relationship: CoachingRelationshipWithUserNames, + style: SessionTitleStyle, + locale: string +): SessionTitle { + var sessionTitleStr = ""; + + if (style == SessionTitleStyle.CoachFirstCoacheeFirst) { + sessionTitleStr = constructCoachFirstCoacheeFirstTitle( + relationship.coach_first_name, + relationship.coachee_first_name + ); + } else if (style == SessionTitleStyle.CoachFirstLastCoacheeFirstLast) { + sessionTitleStr = constructCoachFirstLastCoacheeFirstLastTitle( + relationship.coach_first_name, + relationship.coach_last_name, + relationship.coachee_first_name, + relationship.coachee_last_name + ); + } else if (style == SessionTitleStyle.CoachFirstCoacheeFirstDate) { + sessionTitleStr = constructCoachFirstCoacheeFirstDate( + relationship.coach_first_name, + relationship.coachee_first_name, + session.date, + locale + ); + } else if (style == SessionTitleStyle.CoachFirstCoacheeFirstDateTime) { + sessionTitleStr = constructCoachFirstCoacheeFirstDateTime( + relationship.coach_first_name, + relationship.coachee_first_name, + session.date, + locale + ); + } + + return { + title: sessionTitleStr, + style: style, + }; +} + +export function defaultSessionTitle(): SessionTitle { + return { + title: "Untitled Session", + style: siteConfig.titleStyle, + }; +} + +export function sessionTitleToString(sessionTitle: SessionTitle): string { + return JSON.stringify(sessionTitle); +}