Skip to content

Commit

Permalink
Agreements are now fully working in an optimized modular architecture…
Browse files Browse the repository at this point in the history
… between the page and the component.
  • Loading branch information
jhodapp committed Sep 23, 2024
1 parent 1afdfce commit 8a68b4f
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 56 deletions.
47 changes: 45 additions & 2 deletions src/app/coaching-sessions/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -94,6 +99,40 @@ export default function CoachingSessionsPage() {
fetchNote();
}, [coachingSessionId, noteId]);

const handleAgreementAdded = (body: string): Promise<Agreement> => {
// 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<Agreement> => {
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<Agreement> => {
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);

Expand Down Expand Up @@ -206,6 +245,10 @@ export default function CoachingSessionsPage() {
<AgreementsList
coachingSessionId={coachingSessionId}
userId={userId}
locale={siteConfig.locale}
onAgreementAdded={handleAgreementAdded}
onAgreementEdited={handleAgreementEdited}
onAgreementDeleted={handleAgreementDeleted}
></AgreementsList>
</div>
</TabsContent>
Expand Down
136 changes: 86 additions & 50 deletions src/components/ui/coaching-sessions/agreements-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Agreement>;
onAgreementEdited: (id: Id, value: string) => Promise<Agreement>;
onAgreementDeleted: (id: Id) => Promise<Agreement>;
}> = ({
coachingSessionId,
userId,
locale,
onAgreementAdded,
onAgreementEdited,
onAgreementDeleted,
}) => {
enum AgreementSortField {
Body = "body",
CreatedAt = "created_at",
UpdatedAt = "updated_at",
}

const [agreements, setAgreements] = useState<Agreement[]>([]);
const [newAgreement, setNewAgreement] = useState("");
const [editingId, setEditingId] = useState<Id | null>(null);
const [editBody, setEditBody] = useState("");
const [sortColumn, setSortColumn] = useState<keyof Agreement>("created_at");
const [sortColumn, setSortColumn] = useState<keyof Agreement>(
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);
Expand All @@ -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;
})
Expand All @@ -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;
});
};
Expand Down Expand Up @@ -150,29 +173,42 @@ const AgreementsList: React.FC<{
}, [coachingSessionId]);

return (
<div className="flex">
<div>
<div className="bg-inherit rounded-lg border border-gray-200 p-6">
<div className="mb-4">
<Table>
<TableHeader>
<TableRow>
<TableHead
onClick={() => sortAgreements("body")}
className="cursor-pointer"
onClick={() => sortAgreements(AgreementSortField.Body)}
className={`cursor-pointer ${
sortColumn === AgreementSortField.Body
? "underline"
: "no-underline"
}`}
>
Agreement <ArrowUpDown className="ml-2 h-4 w-4 inline" />
</TableHead>
<TableHead
onClick={() => 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 <ArrowUpDown className="ml-2 h-4 w-4 inline" />
Created
<ArrowUpDown className="ml-2 h-4 w-4 inline" />
</TableHead>
<TableHead
onClick={() => 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 <ArrowUpDown className="ml-2 h-4 w-4 inline" />
Updated <ArrowUpDown className="ml-2 h-4 w-4 inline" />
</TableHead>
<TableHead className="w-[100px]"></TableHead>
</TableRow>
Expand Down Expand Up @@ -205,15 +241,15 @@ const AgreementsList: React.FC<{
agreement.body
)}
</TableCell>
<TableCell className="hidden md:table-cell">
{agreement.created_at.toLocaleString(
DateTime.DATETIME_FULL
)}
<TableCell className="hidden sm:table-cell">
{agreement.created_at
.setLocale(siteConfig.locale)
.toLocaleString(DateTime.DATETIME_MED)}
</TableCell>
<TableCell className="hidden md:table-cell">
{agreement.updated_at.toLocaleString(
DateTime.DATETIME_FULL
)}
{agreement.updated_at
.setLocale(siteConfig.locale)
.toLocaleString(DateTime.DATETIME_MED)}
</TableCell>
<TableCell>
<DropdownMenu>
Expand Down
9 changes: 5 additions & 4 deletions src/lib/api/agreements.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/site.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down

0 comments on commit 8a68b4f

Please sign in to comment.