diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index 55a047c..17c5997 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -48,9 +48,14 @@ import { import { Note, noteToString } from "@/types/note"; import { useAuthStore } from "@/lib/providers/auth-store-provider"; import { Id } from "@/types/general"; -//import { Agreements } from "@/components/ui/coaching-sessions/agreements"; import { AgreementsList } from "@/components/ui/coaching-sessions/agreements-list"; -import { Agreement } from "@/types/agreement"; +import { Agreement, agreementToString } from "@/types/agreement"; +import { + createAgreement, + deleteAgreement, + updateAgreement, +} from "@/lib/api/agreements"; +import { siteConfig } from "@/site.config"; // export const metadata: Metadata = { // title: "Coaching Session", @@ -94,6 +99,40 @@ export default function CoachingSessionsPage() { fetchNote(); }, [coachingSessionId, noteId]); + const handleAgreementAdded = (body: string): Promise => { + // Calls the backend endpoint that creates and stores a full Agreement entity + return createAgreement(coachingSessionId, userId, body) + .then((agreement) => { + return agreement; + }) + .catch((err) => { + console.error("Failed to create new Agreement: " + err); + throw err; + }); + }; + + const handleAgreementEdited = (id: Id, body: string): Promise => { + return updateAgreement(id, coachingSessionId, userId, body) + .then((agreement) => { + return agreement; + }) + .catch((err) => { + console.error("Failed to update Agreement (id: " + id + "): " + err); + throw err; + }); + }; + + const handleAgreementDeleted = (id: Id): Promise => { + return deleteAgreement(id) + .then((agreement) => { + return agreement; + }) + .catch((err) => { + console.error("Failed to update Agreement (id: " + id + "): " + err); + throw err; + }); + }; + const handleInputChange = (value: string) => { setNote(value); @@ -206,6 +245,10 @@ export default function CoachingSessionsPage() { diff --git a/src/components/ui/coaching-sessions/agreements-list.tsx b/src/components/ui/coaching-sessions/agreements-list.tsx index 192fc76..94897b5 100644 --- a/src/components/ui/coaching-sessions/agreements-list.tsx +++ b/src/components/ui/coaching-sessions/agreements-list.tsx @@ -27,39 +27,50 @@ import { } from "@/lib/api/agreements"; import { Agreement, agreementToString } from "@/types/agreement"; import { DateTime } from "ts-luxon"; +import { siteConfig } from "@/site.config"; const AgreementsList: React.FC<{ coachingSessionId: Id; userId: Id; -}> = ({ coachingSessionId, userId }) => { + locale: string | "us"; + onAgreementAdded: (value: string) => Promise; + onAgreementEdited: (id: Id, value: string) => Promise; + onAgreementDeleted: (id: Id) => Promise; +}> = ({ + coachingSessionId, + userId, + locale, + onAgreementAdded, + onAgreementEdited, + onAgreementDeleted, +}) => { + enum AgreementSortField { + Body = "body", + CreatedAt = "created_at", + UpdatedAt = "updated_at", + } + const [agreements, setAgreements] = useState([]); const [newAgreement, setNewAgreement] = useState(""); const [editingId, setEditingId] = useState(null); const [editBody, setEditBody] = useState(""); - const [sortColumn, setSortColumn] = useState("created_at"); + const [sortColumn, setSortColumn] = useState( + AgreementSortField.CreatedAt + ); const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc"); const addAgreement = () => { if (newAgreement.trim() === "") return; - // TODO: move this and the other backend calls outside of this component and trigger - // an event instead, especially if we end up making this a reusable Agreement/Action component. - createAgreement(coachingSessionId, userId, newAgreement) + // Call the external onAgreementAdded handler function which should + // store this agreement in the backend database + onAgreementAdded(newAgreement) .then((agreement) => { console.trace( - "Newly created Agreement: " + agreementToString(agreement) + "Newly created Agreement (onAgreementAdded): " + + agreementToString(agreement) ); - setAgreements((prevAgreements) => [ - ...prevAgreements, - { - id: agreement.id, - coaching_session_id: agreement.coaching_session_id, - body: agreement.body, - user_id: agreement.user_id, - created_at: agreement.created_at, - updated_at: agreement.updated_at, - }, - ]); + setAgreements((prevAgreements) => [...prevAgreements, agreement]); }) .catch((err) => { console.error("Failed to create new Agreement: " + err); @@ -70,23 +81,30 @@ const AgreementsList: React.FC<{ }; const updateAgreement = async (id: Id, newBody: string) => { + const body = newBody.trim(); + if (body === "") return; + try { const updatedAgreements = await Promise.all( agreements.map(async (agreement) => { if (agreement.id === id) { - // Call the async updateAgreement function - const updatedAgreement = await updateAgreementApi( - id, - agreement.user_id, - agreement.coaching_session_id, - newBody - ); + // Call the external onAgreementEdited handler function which should + // update the stored version of this agreement in the backend database + agreement = await onAgreementEdited(id, body) + .then((updatedAgreement) => { + console.trace( + "Updated Agreement (onAgreementUpdated): " + + agreementToString(updatedAgreement) + ); - return { - ...updatedAgreement, - body: newBody, - updated_at: DateTime.now(), - }; + return updatedAgreement; + }) + .catch((err) => { + console.error( + "Failed to update Agreement (id: " + id + "): " + err + ); + throw err; + }); } return agreement; }) @@ -96,21 +114,26 @@ const AgreementsList: React.FC<{ setEditingId(null); setEditBody(""); } catch (err) { - console.error("Failed to update Agreement:", err); + console.error("Failed to update Agreement (id: " + id + "): ", err); throw err; - // Handle error (e.g., show an error message to the user) } }; const deleteAgreement = (id: Id) => { - deleteAgreementApi(id) - .then((deleted_id) => { - console.trace("Deleted Agreement id: " + JSON.stringify(deleted_id)); + if (id === "") return; + // Call the external onAgreementDeleted handler function which should + // delete this agreement from the backend database + onAgreementDeleted(id) + .then((agreement) => { + console.trace( + "Deleted Agreement (onAgreementDeleted): " + + agreementToString(agreement) + ); setAgreements(agreements.filter((agreement) => agreement.id !== id)); }) .catch((err) => { - console.error("Failed to create new Agreement: " + err); + console.error("Failed to Agreement (id: " + id + "): " + err); throw err; }); }; @@ -150,29 +173,42 @@ const AgreementsList: React.FC<{ }, [coachingSessionId]); return ( -
+
sortAgreements("body")} - className="cursor-pointer" + onClick={() => sortAgreements(AgreementSortField.Body)} + className={`cursor-pointer ${ + sortColumn === AgreementSortField.Body + ? "underline" + : "no-underline" + }`} > Agreement sortAgreements("created_at")} - className="cursor-pointer hidden md:table-cell" + className={`cursor-pointer hidden sm:table-cell ${ + sortColumn === AgreementSortField.CreatedAt + ? "underline" + : "no-underline" + }`} > - Created At + Created + sortAgreements("updated_at")} - className="cursor-pointer hidden md:table-cell" + className={`cursor-pointer hidden md:table-cell ${ + sortColumn === AgreementSortField.UpdatedAt + ? "underline" + : "no-underline" + }`} + onClick={() => sortAgreements(AgreementSortField.UpdatedAt)} > - Last Updated + Updated @@ -205,15 +241,15 @@ const AgreementsList: React.FC<{ agreement.body )} - - {agreement.created_at.toLocaleString( - DateTime.DATETIME_FULL - )} + + {agreement.created_at + .setLocale(siteConfig.locale) + .toLocaleString(DateTime.DATETIME_MED)} - {agreement.updated_at.toLocaleString( - DateTime.DATETIME_FULL - )} + {agreement.updated_at + .setLocale(siteConfig.locale) + .toLocaleString(DateTime.DATETIME_MED)} diff --git a/src/lib/api/agreements.ts b/src/lib/api/agreements.ts index 3d4ccb8..96d93de 100644 --- a/src/lib/api/agreements.ts +++ b/src/lib/api/agreements.ts @@ -1,4 +1,4 @@ -// Interacts with the note endpoints +// Interacts with the agreement endpoints import { Agreement, defaultAgreement, isAgreement, isAgreementArray, parseAgreement } from "@/types/agreement"; import { CompletionStatus, Id } from "@/types/general"; @@ -136,9 +136,10 @@ export const createAgreement = async ( }, }) .then(function (response: AxiosResponse) { - // handle success - if (isAgreement(response.data.data)) { - updatedAgreement = response.data.data; + // handle success + const agreement_data = response.data.data; + if (isAgreement(agreement_data)) { + updatedAgreement = parseAgreement(agreement_data); } }) .catch(function (error: AxiosError) { diff --git a/src/site.config.ts b/src/site.config.ts index d4176e3..be3c703 100644 --- a/src/site.config.ts +++ b/src/site.config.ts @@ -2,6 +2,7 @@ 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: {