Skip to content

Commit

Permalink
Merge pull request #175 from cabcookie:improve-person-mention
Browse files Browse the repository at this point in the history
feat: recommend projects to add to meeting notes
  • Loading branch information
cabcookie authored Aug 8, 2024
2 parents 4beadb4 + d891cd1 commit e383573
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 31 deletions.
1 change: 1 addition & 0 deletions api/ContextProjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ export const ProjectsContextProvider: FC<ProjectsContextProviderProps> = ({
? ""
: flow(
filter((p: Project) => projectIds.includes(p.id)),
filter((p) => !p.done),
map(get("project")),
join(", ")
)(projects);
Expand Down
26 changes: 26 additions & 0 deletions api/useMeeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,31 @@ const useMeeting = (meetingId?: string) => {
return data?.meetingId;
};

const getMeetingParticipantId = (personId: string) => {
if (!meeting) return;
return meeting.participantMeetingIds[
meeting.participantIds.findIndex((id) => id === personId)
];
};

const removeMeetingParticipant = async (personId: string) => {
if (!meeting) return;
const updated: Meeting = {
...meeting,
participantIds: meeting.participantIds.filter((id) => id !== personId),
};
mutateMeeting(updated, false);
const meetingParticipantId = getMeetingParticipantId(personId);
if (!meetingParticipantId) return;
const { data, errors } = await client.models.MeetingParticipant.delete({
id: meetingParticipantId,
});
if (errors)
handleApiErrors(errors, "Error deleting entry meeting participant");
mutateMeeting(updated);
return data?.id;
};

const deleteMeetingActivity = async (activityId: string) => {
if (!meeting) return;
const updated: Meeting = {
Expand Down Expand Up @@ -164,6 +189,7 @@ const useMeeting = (meetingId?: string) => {
loadingMeeting,
updateMeeting,
createMeetingParticipant,
removeMeetingParticipant,
createMeetingActivity,
updateMeetingContext,
deleteMeetingActivity,
Expand Down
47 changes: 47 additions & 0 deletions api/useMeetingProjectRecommendation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { type Schema } from "@/amplify/data/resource";
import { generateClient } from "aws-amplify/data";
import { flatMap, flatten, flow, get, uniq } from "lodash/fp";
import useSWR from "swr";
import { handleApiErrors } from "./globals";
import { Meeting } from "./useMeetings";
const client = generateClient<Schema>();

const fetchPerson = async (personId: string) => {
const { data, errors } = await client.models.Person.get(
{ id: personId },
{
selectionSet: ["meetings.meeting.activities.forProjects.projectsId"],
}
);
if (errors) {
handleApiErrors(errors, "Error loading meeting project recommendations");
throw errors;
}
return flow(
get("meetings"),
flatMap(get("meeting.activities")),
flatMap(get("forProjects")),
flatMap(get("projectsId"))
)(data);
};

const fetchMeetingProjectRecommendation = (meeting?: Meeting) => async () => {
if (!meeting) return;
const data = await Promise.all(meeting.participantIds.map(fetchPerson));
return flow(flatten, uniq)(data) as string[] | undefined;
};

const useMeetingProjectRecommendation = (meeting?: Meeting) => {
const {
data: projectIds,
isLoading,
error,
} = useSWR(
`/api/meetings/${meeting?.id}/recommendations`,
fetchMeetingProjectRecommendation(meeting)
);

return { projectIds, isLoading, error };
};

export default useMeetingProjectRecommendation;
8 changes: 6 additions & 2 deletions api/useMeetings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type Meeting = {
topic: string;
context?: Context;
meetingOn: Date;
participantMeetingIds: string[];
participantIds: string[];
activities: Activity[];
};
Expand All @@ -25,7 +26,8 @@ export const meetingSelectionSet = [
"context",
"meetingOn",
"createdAt",
"participants.person.id",
"participants.id",
"participants.personId",
"activities.id",
"activities.notes",
"activities.formatVersion",
Expand Down Expand Up @@ -57,7 +59,8 @@ export const mapMeeting: (data: MeetingData) => Meeting = ({
topic,
meetingOn: new Date(meetingOn || createdAt),
context: context || undefined,
participantIds: participants.map(({ person: { id } }) => id),
participantMeetingIds: participants.map(({ id }) => id),
participantIds: participants.map(({ personId }) => personId),
activities: flow(
map(mapActivity),
sortBy((a) => -a.finishedOn.getTime())
Expand Down Expand Up @@ -169,6 +172,7 @@ const useMeetings = ({ page = 1, context }: UseMeetingsProps) => {
topic,
meetingOn: new Date(),
participantIds: [],
participantMeetingIds: [],
activities: [],
};
const updatedMeetings = [newMeeting, ...(meetings || [])];
Expand Down
8 changes: 7 additions & 1 deletion components/meetings/meeting-participants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import PeopleSelector from "../ui-elements/selectors/people-selector";
type MeetingParticipantsProps = {
participantIds: string[];
addParticipant?: (personId: string | null) => void;
removeParticipant?: (personId: string) => void;
};

const MeetingParticipants: FC<MeetingParticipantsProps> = ({
participantIds,
addParticipant,
removeParticipant,
}) => {
const { getNamesByIds } = usePeople();

Expand All @@ -32,7 +34,11 @@ const MeetingParticipants: FC<MeetingParticipantsProps> = ({
)}
<div className="my-2" />

<PeopleList personIds={participantIds} showNotes={false} />
<PeopleList
personIds={participantIds}
showNotes={false}
onDelete={removeParticipant}
/>
</DefaultAccordionItem>
)
);
Expand Down
53 changes: 53 additions & 0 deletions components/meetings/meeting-project-recommender.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useAccountsContext } from "@/api/ContextAccounts";
import { useProjectsContext } from "@/api/ContextProjects";
import useMeetingProjectRecommendation from "@/api/useMeetingProjectRecommendation";
import { Meeting } from "@/api/useMeetings";
import { compact, filter, flatMap, flow, get, map } from "lodash/fp";
import { FC } from "react";

const filterOutProjectIds = (avoidIds: string[] | undefined) => (id: string) =>
!avoidIds?.includes(id);

type MeetingProjectRecommenderProps = {
meeting?: Meeting | undefined;
addProjectToMeeting: (projectId: string) => void;
};

const MeetingProjectRecommender: FC<MeetingProjectRecommenderProps> = ({
meeting,
addProjectToMeeting,
}) => {
const { projectIds } = useMeetingProjectRecommendation(meeting);
const { getProjectById } = useProjectsContext();
const { getAccountNamesByIds } = useAccountsContext();

return (
<div className="px-1 md:px-2 text-sm text-muted-foreground">
<span className="font-semibold">
Add these projects where participants contributed:
</span>
{flow(
filter(
filterOutProjectIds(
flow(get("activities"), flatMap(get("projectIds")))(meeting)
)
),
map(getProjectById),
compact,
filter((p) => !p.done),
map(({ id, project, accountIds }) => (
<span
key={id}
className="pl-1 md:pl-2 hover:text-primary hover:underline hover:underline-offset-2 hover:cursor-pointer"
onClick={() => addProjectToMeeting(id)}
>
{project}
{accountIds && ` (${getAccountNamesByIds(accountIds)})`}
</span>
))
)(projectIds)}
</div>
);
};

export default MeetingProjectRecommender;
10 changes: 10 additions & 0 deletions components/meetings/meeting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Accordion } from "../ui/accordion";
import MeetingActivityList from "./meeting-activity-list";
import MeetingNextActions from "./meeting-next-actions";
import MeetingParticipants from "./meeting-participants";
import MeetingProjectRecommender from "./meeting-project-recommender";

type MeetingRecordProps = {
meeting?: Meeting;
Expand All @@ -35,6 +36,7 @@ const MeetingRecord: FC<MeetingRecordProps> = ({
const [meetingContext, setMeetingContext] = useState(meeting?.context);
const {
createMeetingActivity,
removeMeetingParticipant,
updateMeetingContext,
updateMeeting,
createMeetingParticipant,
Expand Down Expand Up @@ -116,6 +118,13 @@ const MeetingRecord: FC<MeetingRecordProps> = ({
/>
)}

{addProjects && (
<MeetingProjectRecommender
meeting={meeting}
addProjectToMeeting={handleSelectProject}
/>
)}

<Accordion type="single" collapsible>
{showMeetingDate &&
(!meeting ? (
Expand Down Expand Up @@ -143,6 +152,7 @@ const MeetingRecord: FC<MeetingRecordProps> = ({
<MeetingParticipants
participantIds={meeting.participantIds}
addParticipant={!addParticipants ? undefined : addParticipant}
removeParticipant={removeMeetingParticipant}
/>

<MeetingNextActions meeting={meeting} />
Expand Down
8 changes: 7 additions & 1 deletion components/people/PeopleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import PersonDetails from "./PersonDetails";
type PeopleListProps = {
personIds?: string[];
showNotes?: boolean;
onDelete?: (personId: string) => void;
};

const PeopleList: FC<PeopleListProps> = ({ personIds, showNotes }) => {
const PeopleList: FC<PeopleListProps> = ({
personIds,
showNotes,
onDelete,
}) => {
const { people } = usePeople();

const personName = (person?: Person) =>
Expand All @@ -32,6 +37,7 @@ const PeopleList: FC<PeopleListProps> = ({ personIds, showNotes }) => {
filter((a: PersonAccount) => a.isCurrent),
flatMap((a) => [a.position, a.accountName])
)(people)}
onDelete={() => onDelete?.(personId)}
link={`/people/${personId}`}
>
<PersonDetails personId={personId} showNotes={showNotes} />
Expand Down
34 changes: 10 additions & 24 deletions components/ui-elements/project-notes-form/project-notes-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,30 +64,16 @@ const ProjectNotesForm: FC<ProjectNotesFormProps> = ({
<DeleteWarning
open={openDeleteActivityConfirmation}
onOpenChange={setOpenDeleteActivityConfirmation}
confirmText={
<>
<p>
Are you sure you want to delete the activity with the following
information?
</p>
{activity?.projectIds.map((id) => (
<p key={id}>
<small>Project: {getProjectById(id)?.project}</small>
</p>
))}
{activity && (
<p>
<small>
Notes:{" "}
{getTextFromEditorJsonContent(activity?.notes).slice(
0,
200
)}
</small>
</p>
)}
</>
}
confirmText={`Are you sure you want to delete the activity with the following information? ${activity?.projectIds.map(
(id) =>
`Project: ${getProjectById(id)?.project}${
activity &&
`; Notes: ${getTextFromEditorJsonContent(activity?.notes).slice(
0,
200
)}`
}`
)}`}
onConfirm={deleteActivity}
/>
)}
Expand Down
9 changes: 6 additions & 3 deletions docs/releases/next.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Fehlerbehebungen CRM Projekte und Hauptmenü (Version :VERSION)
# Meetings weiter optimieren (Version :VERSION)

- Es gab CRM Projekte, bei denen das Abschlussdatum weit in der Zukunft lag und das CRM Projekt bereits geschlossen ist (verloren oder gewonnen). In diesem Fall wäre das Projekt noch sehr lange und immer wieder in der Liste aufgetaucht, obwohl nichts mehr dafür getan werden konnte. Das ist nun behoben. Es wird immer das kleinere Datum für den Import herangezogen, entweder das Abschlussdatum oder das System-Abschlussdatum, das anzeigt, wann das Projekt geschlossen wurde.
- Das Hauptmenü führte häufig zu Abstürzen, wenn der Suchbegriff einmal mehr als 3 Zeichen hatte und dann wieder weniger. Der Grund dafür war, dass die Gruppen als Komponenten immer erst dann erschienen, wenn die Suche mehr als 3 Zeichen hatte. CmdK kann nicht gut damit umgehen, wenn die React Komponente plötzlich wieder ganz verschwindet. Wir verstecken sie nun mit einer CSS Klasse, entfernen sie aber nicht komplett vom DOM.
- In Meetings werden Projekte angezeigt, bei denen die Teilnehmer schon einmal in Meetings involviert gewesen sind. Wenn ein Projekt angeklickt wird, wird es direkt dem Meeting hinzugefügt und man kann anfangen, Notizen zu machen.
- In Meetings können Teilnehmer wieder entfernt werden, wenn man sie versehentlich hinzugefügt hat.
- In der Detailansicht einer Person werden im Untertitel von Notizen nur noch Projekte angezeigt, in die die Person involviert ist, die aber noch nicht abgeschlossen sind.

## In Arbeit

## Geplant

- Blöcke als einzelne Records in der Datenbank speichern.

0 comments on commit e383573

Please sign in to comment.