Skip to content

Commit

Permalink
Merge pull request #177 from cabcookie:improve-person-mention
Browse files Browse the repository at this point in the history
Benutzerprofil mit Person verknüpfen und vorgeschlagene Projekte filtern, wenn nur Externe
  • Loading branch information
cabcookie authored Aug 10, 2024
2 parents 0bc54e5 + 1f6a7ee commit 8875084
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 55 deletions.
3 changes: 3 additions & 0 deletions amplify/data/person-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ const personSchmema = {
email: a.string(),
name: a.string(),
profilePicture: a.string(),
personId: a.id(),
person: a.belongsTo("Person", "personId"),
})
.identifier(["profileId"])
.authorization((allow) => [allow.ownerDefinedIn("profileId")]),
Expand All @@ -85,6 +87,7 @@ const personSchmema = {
accounts: a.hasMany("PersonAccount", "personId"),
details: a.hasMany("PersonDetail", "personId"),
learnings: a.hasMany("PersonLearning", "personId"),
profile: a.hasOne("User", "personId"),
})
.authorization((allow) => [allow.owner()]),
};
Expand Down
10 changes: 10 additions & 0 deletions api/usePerson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,15 @@ const usePerson = (personId?: string) => {
isLoading: loadingPerson,
} = useSWR(`/api/person/${personId}`, fetchPerson(personId));

const deletePerson = async () => {
if (!person) return;
const { data, errors } = await client.models.Person.delete({
id: person.id,
});
if (errors) handleApiErrors(errors, "Deleting person failed");
return data?.id;
};

const updatePerson = async ({
name,
howToSay,
Expand Down Expand Up @@ -456,6 +465,7 @@ const usePerson = (personId?: string) => {
errorPerson,
loadingPerson,
updatePerson,
deletePerson,
createPersonAccount,
deletePersonAccount,
updatePersonAccount,
Expand Down
72 changes: 61 additions & 11 deletions api/useUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { type Schema } from "@/amplify/data/resource";
import { toast } from "@/components/ui/use-toast";
import { uploadFileToS3 } from "@/helpers/s3/upload-filtes";
import { AuthUser, getCurrentUser } from "aws-amplify/auth";
import { generateClient } from "aws-amplify/data";
import { generateClient, SelectionSet } from "aws-amplify/data";
import { remove } from "aws-amplify/storage";
import { isFuture } from "date-fns";
import { filter, first, flow, get, map, sortBy } from "lodash/fp";
import useSWR from "swr";
import { handleApiErrors } from "./globals";
const client = generateClient<Schema>();
Expand All @@ -12,30 +14,59 @@ export type TUpdateProfileInfo = {
displayName: string;
};

const selectionSet = [
"name",
"profilePicture",
"profileId",
"personId",
"person.name",
"person.accounts.accountId",
"person.accounts.startDate",
"person.accounts.endDate",
] as const;

type UserData = SelectionSet<Schema["User"]["type"], typeof selectionSet>;
type AccountData = UserData["person"]["accounts"][number];

export type User = {
loginId?: string;
userId: string;
userName?: string;
profilePicture?: string;
hasNoProfile: boolean;
personId?: string;
currentAccountId?: string;
};

const mapUser = (
user: AuthUser,
profileData: Schema["User"]["type"] | undefined | null
): User => ({
const mapUser = (user: AuthUser, profileData: UserData | null): User => ({
loginId: user.signInDetails?.loginId,
userId: user.username,
userName: profileData?.name || undefined,
profilePicture: profileData?.profilePicture || undefined,
userName: profileData?.person?.name ?? profileData?.name ?? undefined,
profilePicture: profileData?.profilePicture ?? undefined,
hasNoProfile: !profileData?.profileId,
personId: profileData?.personId ?? undefined,
currentAccountId: flow(
get("person.accounts"),
filter((a: AccountData) => !a.endDate || isFuture(new Date(a.endDate))),
map((a) => ({
accountId: a.accountId,
startDate: !a.startDate ? undefined : new Date(a.startDate),
endDate: !a.endDate ? undefined : new Date(a.endDate),
})),
sortBy((a) => -(a.startDate?.getTime() || 0)),
first,
get("accountId")
)(profileData),
});

const fetchUser = async () => {
const user = await getCurrentUser();
const { data, errors } = await client.models.User.get({
profileId: `${user.username}::${user.username}`,
});
const { data, errors } = await client.models.User.get(
{
profileId: `${user.username}::${user.username}`,
},
{ selectionSet }
);
if (errors) {
handleApiErrors(errors, "Error loading user");
throw errors;
Expand Down Expand Up @@ -108,7 +139,26 @@ const useCurrentUser = () => {
return data.profileId;
};

return { user, createProfile, updateProfileInfo, updateProfilePicture };
const linkPersonToUser = async (personId: string | null) => {
if (!user) return;
const updated: User = { ...user, personId: personId ?? undefined };
mutate(updated, false);
const { data, errors } = await client.models.User.update({
profileId: `${user.userId}::${user.userId}`,
personId,
});
if (errors) handleApiErrors(errors, "Linking person to profile failed");
mutate(updated);
return data?.profileId;
};

return {
user,
createProfile,
updateProfileInfo,
updateProfilePicture,
linkPersonToUser,
};
};

export default useCurrentUser;
95 changes: 77 additions & 18 deletions components/meetings/meeting-project-recommender.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
import { useAccountsContext } from "@/api/ContextAccounts";
import { useProjectsContext } from "@/api/ContextProjects";
import { Project, 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";
import usePeople from "@/api/usePeople";
import { Person } from "@/api/usePerson";
import useCurrentUser, { User } from "@/api/useUser";
import { compact, filter, flatMap, flow, get, map, uniq } from "lodash/fp";
import { FC, useEffect, useState } from "react";

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

const getPersonById = (people: Person[] | undefined) => (personId: string) =>
people?.find((p) => p.id === personId);

const getUniqAccountIds =
(people: Person[] | undefined) =>
(meeting: Meeting | undefined): string[] | undefined =>
flow(
get("participantIds"),
map(getPersonById(people)),
flatMap(get("accounts")),
map(get("accountId")),
uniq
)(meeting);

const getNonInternalAccountIds =
(user: User | undefined, people: Person[] | undefined) =>
(meeting: Meeting | undefined): string[] | undefined =>
flow(
getUniqAccountIds(people),
filter((id) => !!id && id !== user?.currentAccountId)
)(meeting);

const filterProjecsByAccountIds =
(projects: Project[] | undefined) =>
(accountIds: string[] | undefined): Project[] | undefined =>
flow(
filter(
(p: Project) =>
!accountIds ||
accountIds.length === 0 ||
p.accountIds.some((accountId) => accountIds.includes(accountId))
)
)(projects);

const filterInternalProjects =
(
meeting: Meeting | undefined,
user: User | undefined,
people: Person[] | undefined
) =>
(projects: Project[]): Project[] | undefined =>
flow(
getNonInternalAccountIds(user, people),
filterProjecsByAccountIds(projects)
)(meeting);

type MeetingProjectRecommenderProps = {
meeting?: Meeting | undefined;
addProjectToMeeting: (projectId: string) => void;
Expand All @@ -20,22 +69,32 @@ const MeetingProjectRecommender: FC<MeetingProjectRecommenderProps> = ({
const { projectIds } = useMeetingProjectRecommendation(meeting);
const { getProjectById } = useProjectsContext();
const { getAccountNamesByIds } = useAccountsContext();
const [projects, setProjects] = useState<Project[] | undefined>();
const { people } = usePeople();
const { user } = useCurrentUser();

useEffect(() => {
flow(
filter(
filterOutProjectIds(
flow(get("activities"), flatMap(get("projectIds")))(meeting)
)
),
map(getProjectById),
compact,
filter((p: Project) => !p.done),
filterInternalProjects(meeting, user, people),
setProjects
)(projectIds);
}, [meeting, projectIds, user, people, getProjectById]);

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 }) => (
<div>
<span className="font-semibold">
Add these projects where participants contributed:
</span>
{projects?.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"
Expand All @@ -44,8 +103,8 @@ const MeetingProjectRecommender: FC<MeetingProjectRecommenderProps> = ({
{project}
{accountIds && ` (${getAccountNamesByIds(accountIds)})`}
</span>
))
)(projectIds)}
))}
</div>
</div>
);
};
Expand Down
30 changes: 29 additions & 1 deletion components/people/PersonDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import usePerson from "@/api/usePerson";
import { FC } from "react";
import { useRouter } from "next/router";
import { FC, useState } from "react";
import DeleteWarning from "../ui-elements/project-notes-form/DeleteWarning";
import { Accordion } from "../ui/accordion";
import { Button } from "../ui/button";
import PersonAccounts from "./PersonAccounts";
import PersonContactDetails from "./PersonContactDetails";
import PersonDates from "./PersonDates";
Expand All @@ -25,13 +28,22 @@ const PersonDetails: FC<PersonDetailsProps> = ({
const {
person,
updatePerson,
deletePerson,
createPersonAccount,
deletePersonAccount,
updatePersonAccount,
createContactDetail,
updateContactDetail,
deleteContactDetail,
} = usePerson(personId);
const [deleteWarningOpen, setDeleteWarningOpen] = useState(false);
const router = useRouter();

const handlePersonDelete = async () => {
const result = await deletePerson();
if (!result) return;
router.push("/");
};

return (
<>
Expand All @@ -45,6 +57,22 @@ const PersonDetails: FC<PersonDetailsProps> = ({
</div>
)}

<DeleteWarning
open={deleteWarningOpen}
onOpenChange={setDeleteWarningOpen}
confirmText="Are you sure you want to delete the person?"
onConfirm={handlePersonDelete}
/>

<div>
<Button
onClick={() => setDeleteWarningOpen(true)}
disabled={deleteWarningOpen}
>
Delete
</Button>
</div>

<Accordion type="single" collapsible>
<PersonAccounts
person={person}
Expand Down
6 changes: 4 additions & 2 deletions docs/releases/next.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Meetings weiter optimieren (Version :VERSION)
# Benutzerprofil mit Person verknüpfen und vorgeschlagene Projekte filtern, wenn nur Externe (Version :VERSION)

- Statt "Meeting notes" steht nun das konkrete Projekt im Kopf des Accordions und die vereinbarten nächsten Schritte im Untertitel.
- Das eigene Benutzerprofil kann nun mit einer Person in der Personenliste verknüpft werden. Darüber wird dann auch ermittelt, zu welcher Firma der Benutzer aktuell gehört, um besser unterscheiden zu können, ob ein Meeting, das der Benutzer angelegt hat, ein internes Meeting ist oder auch Externe beteiligt sind.
- Personen können nun gelöscht werden. Das ist dann wichtig, wenn man versehentlich ein Duplikat erzeugt hat. Die Auflösung von Duplikaten liegt noch beim Anwender.
- Bei vorgeschlagenenen Projekten in einem Meeting wird berücksichtigt, ob es ein rein internes Meeting ist oder ob Externe dabei sind. Sind Externe dabei, werden nur Projekte vorgeschlagen, an denen die Externen beteilgt sind.

## In Arbeit

Expand Down
Loading

0 comments on commit 8875084

Please sign in to comment.