Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add editing club listed officers and display them #257

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions src/app/manage/[clubId]/edit/officers/EditListedOfficerForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { api } from '@src/trpc/react';
import { editListedOfficerSchema } from '@src/utils/formSchemas';
import { useRouter } from 'next/navigation';
import { useReducer } from 'react';
import {
type FieldErrors,
type UseFormRegister,
useFieldArray,
useForm,
} from 'react-hook-form';
import { type z } from 'zod';

type x = {
id?: boolean;
name?: boolean;
position?: boolean;
}[];
const modifiedFields = (
dirtyFields: x,
data: z.infer<typeof editListedOfficerSchema>,
officers: {
id?: string;
name: string;
position: string;
}[],
) => {
const modded = data.officers.filter(
(value, index) =>
!!officers.find((off) => off.id === value.id) &&
dirtyFields[index]?.position,
);
const created = data.officers.filter(
(value) => typeof value.id === 'undefined',
);
return {
modified: modded as { id: string; name: string; position: string }[],
created: created as { name: string; position: string }[],
};
};

type modifyDeletedAction =
| {
type: 'add';
target: string;
}
| { type: 'reset' };
const deletedReducer = (state: Array<string>, action: modifyDeletedAction) => {
switch (action.type) {
case 'add':
return [...state, action.target];
case 'reset':
return [];
}
};

type EditOfficerFormProps = {
clubId: string;
officers: {
id: string;
name: string;
position: string;
}[];
};
const EditOfficerForm = ({ clubId, officers }: EditOfficerFormProps) => {
const {
control,
register,
handleSubmit,
reset,
formState: { errors, dirtyFields, isDirty },
} = useForm<z.infer<typeof editListedOfficerSchema>>({
resolver: zodResolver(editListedOfficerSchema),
defaultValues: { officers: officers },
});
const { fields, append, remove } = useFieldArray({
control,
name: 'officers',
});
const [deleted, modifyDeleted] = useReducer(deletedReducer, []);
const removeItem = (index: number) => {
const off = officers.find((officer) => officer.id == fields[index]?.id);
if (off) modifyDeleted({ type: 'add', target: off.id });
remove(index);
};
const router = useRouter();
const editOfficers = api.club.edit.listedOfficers.useMutation({
onSuccess: () => {
router.push(`/directory/${clubId}`);
},
});
const submitForm = handleSubmit((data) => {
if (dirtyFields.officers !== undefined) {
const { modified, created } = modifiedFields(
dirtyFields.officers,
data,
officers,
);
if (!editOfficers.isPending) {
editOfficers.mutate({
clubId: clubId,
deleted: deleted,
modified: modified,
created: created,
});
}
}
});
return (
<div className="h-full w-full">
<form onSubmit={submitForm}>
<div className="flex flex-col gap-y-2">
<div className="mb-2 flex flex-row">
<button
className="ml-auto rounded-lg bg-slate-200 p-2"
type="button"
onClick={() => {
append({ name: '', position: '' });
}}
>
add new Officer
</button>
</div>
<div>
{errors.officers && (
<p className="text-red-500">{errors.officers.message}</p>
)}
</div>
<div className="space-y-2">
{fields.map((field, index) => (
<OfficerItem
key={field.id}
register={register}
index={index}
remove={removeItem}
errors={errors}
/>
))}
</div>
</div>
<div className="flex flex-row justify-end gap-x-4 py-2">
<button
className="rounded-lg bg-slate-200 p-1 font-bold"
type="submit"
>
Save Changes
</button>
<button
type="button"
onClick={() => {
reset({
officers: officers,
});
}}
disabled={!isDirty}
className="group relative rounded-lg bg-slate-200 p-1 font-bold"
>
<div className="invisible absolute inset-0 h-full w-full rounded-lg bg-black opacity-80 group-disabled:visible"></div>
<div>Discard Changes</div>
</button>
</div>
</form>
</div>
);
};
export default EditOfficerForm;
type OfficerItemProps = {
register: UseFormRegister<z.infer<typeof editListedOfficerSchema>>;
remove: (index: number) => void;
index: number;
errors: FieldErrors<z.infer<typeof editListedOfficerSchema>>;
};
const OfficerItem = ({ register, index, remove, errors }: OfficerItemProps) => {
return (
<div className="flex flex-row items-center rounded-md bg-slate-300 p-2">
<div className="flex flex-col">
<div>
<input
type="text"
placeholder="Name"
className="mb-1 bg-slate-300 text-xl font-bold text-black"
{...register(`officers.${index}.name` as const)}
aria-invalid={errors.officers && !!errors.officers[index]?.position}
/>
{errors.officers && errors.officers[index]?.position && (
<p className="text-red-500">
{errors.officers[index]?.position?.message}
</p>
)}
</div>
<div>
<input
type="text"
placeholder="Position"
className="bg-slate-300 font-semibold text-black"
{...register(`officers.${index}.position` as const)}
aria-invalid={errors.officers && !!errors.officers[index]?.position}
/>
{errors.officers && errors.officers[index]?.position && (
<p className="text-red-500">
{errors.officers[index]?.position?.message}
</p>
)}
</div>
</div>
<button
className="ml-auto disabled:hidden"
type="button"
onClick={() => remove(index)}
>
remove
</button>
</div>
);
};
38 changes: 2 additions & 36 deletions src/app/manage/[clubId]/edit/officers/EditOfficerForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import { api } from '@src/trpc/react';
import { editOfficerSchema } from '@src/utils/formSchemas';
import { useRouter } from 'next/navigation';
import { useReducer } from 'react';
import {
type FieldErrors,
type UseFormRegister,
useFieldArray,
useForm,
} from 'react-hook-form';
import { useFieldArray, useForm } from 'react-hook-form';
import { type z } from 'zod';

type x = {
Expand Down Expand Up @@ -69,14 +64,12 @@ type EditOfficerFormProps = {
userId: string;
name: string;
locked: boolean;
title: string;
position: 'President' | 'Officer';
}[];
};
const EditOfficerForm = ({ clubId, officers }: EditOfficerFormProps) => {
const {
control,
register,
handleSubmit,
reset,
formState: { errors, dirtyFields, isDirty },
Expand Down Expand Up @@ -143,11 +136,9 @@ const EditOfficerForm = ({ clubId, officers }: EditOfficerFormProps) => {
{fields.map((field, index) => (
<OfficerItem
key={field.id}
register={register}
index={index}
id={field.userId}
remove={removeItem}
errors={errors}
locked={field.locked}
name={field.name}
/>
Expand Down Expand Up @@ -181,23 +172,13 @@ const EditOfficerForm = ({ clubId, officers }: EditOfficerFormProps) => {
};
export default EditOfficerForm;
type OfficerItemProps = {
register: UseFormRegister<z.infer<typeof editOfficerSchema>>;
remove: (index: number, userId: string) => void;
id: string;
index: number;
name: string;
locked: boolean;
errors: FieldErrors<z.infer<typeof editOfficerSchema>>;
};
const OfficerItem = ({
register,
index,
id,
name,
remove,
errors,
locked,
}: OfficerItemProps) => {
const OfficerItem = ({ index, id, name, remove, locked }: OfficerItemProps) => {
return (
<div className="flex flex-row items-center rounded-md bg-slate-300 p-2">
<div className="flex flex-col">
Expand All @@ -206,21 +187,6 @@ const OfficerItem = ({
{name}
</h4>
</div>
<div>
<input
type="text"
placeholder="Position"
className="bg-slate-300 font-semibold text-black"
{...register(`officers.${index}.title` as const)}
aria-invalid={errors.officers && !!errors.officers[index]?.title}
disabled={locked}
/>
{errors.officers && errors.officers[index]?.title && (
<p className="text-red-500">
{errors.officers[index]?.title?.message}
</p>
)}
</div>
</div>
<button
className="ml-auto disabled:hidden"
Expand Down
9 changes: 7 additions & 2 deletions src/app/manage/[clubId]/edit/officers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { api } from '@src/trpc/server';
import { getServerAuthSession } from '@src/server/auth';
import { redirect } from 'next/navigation';
import { signInRoute } from '@src/utils/redirect';
import EditListedOfficerForm from './EditListedOfficerForm';

export default async function Page({
params: { clubId },
Expand All @@ -15,13 +16,13 @@ export default async function Page({
if (!session) redirect(signInRoute(`manage/${clubId}/edit/officers`));
const role = await api.club.memberType({ id: clubId });
const officers = await api.club.getOfficers({ id: clubId });
const listedOfficers = await api.club.getListedOfficers({ id: clubId });

const mapped = officers.map((officer) => ({
userId: officer.userId,
name: officer.userMetadata.firstName + ' ' + officer.userMetadata.lastName,
locked: officer.memberType == 'President' || role == 'Officer',
position: officer.memberType as 'President' | 'Officer',
title: '', // TODO: link from officers table
}));

return (
Expand All @@ -30,9 +31,13 @@ export default async function Page({
<div className="flex flex-col gap-y-2 px-5">
<BlueBackButton />
<h1 className="text-2xl font-extrabold text-blue-primary">
Edit club officers
Edit club Collaborators
</h1>
<EditOfficerForm clubId={clubId} officers={mapped} />
<h1 className="text-2xl font-extrabold text-blue-primary">
Edit club officers
</h1>
<EditListedOfficerForm clubId={clubId} officers={listedOfficers} />
</div>
</main>
);
Expand Down
14 changes: 6 additions & 8 deletions src/components/club/listing/ClubInfoSegment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const ClubInfoSegment: FC<{
club: NonNullable<RouterOutputs['club']['getDirectoryInfo']>;
}> = async ({ club }) => {
const isActive = await api.club.isActive({ id: club.id });
const president = club.userMetadataToClubs.find(
const president = (await api.club.getOfficers({ id: club.id })).find(
(officer) => officer.memberType === 'President',
);
return (
Expand Down Expand Up @@ -52,13 +52,13 @@ const ClubInfoSegment: FC<{
{club.description}
</p>
</div>
{club.userMetadataToClubs.length != 0 && (
{club.officers.length != 0 && (
<div className="min-w-fit">
<>
<h1 className="text-center text-2xl font-medium">Leadership</h1>
<div className="flex flex-col justify-center align-middle">
{club.userMetadataToClubs.map((officer) => (
<div className="mt-5 flex flex-row" key={officer.userId}>
{club.officers.map((officer) => (
<div className="mt-5 flex flex-row" key={officer.id}>
<Image
src={club.image}
alt="Picture of the author"
Expand All @@ -68,12 +68,10 @@ const ClubInfoSegment: FC<{
/>
<div className="mx-5 flex flex-col justify-center align-middle">
<p className="text-left text-sm text-slate-600">
{officer.userMetadata.firstName +
' ' +
officer.userMetadata.lastName}
{officer.name}
</p>
<p className="mt-2 text-sm text-slate-400">
Officer {/* TODO: link to officers table */}
{officer.position}
</p>
</div>
</div>
Expand Down
Loading