Skip to content

Commit 47c98af

Browse files
heydoyouknowme0aryansri-19
authored andcommitted
feat: faculty section edit
1 parent df6cab2 commit 47c98af

File tree

10 files changed

+889
-82
lines changed

10 files changed

+889
-82
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use client';
2+
import { Button } from '~/components/buttons';
3+
import { Dialog } from '~/components/dialog';
4+
import { Card, CardFooter, CardHeader } from '~/components/ui';
5+
import { toast } from '~/lib/hooks';
6+
import { cn } from '~/lib/utils';
7+
import { deleteFacultySelectionElement } from '~/server/actions';
8+
9+
export default function Page({
10+
params: { locale },
11+
searchParams: { topic, id },
12+
}: {
13+
params: { locale: string };
14+
searchParams: { topic?: string; id?: string };
15+
}) {
16+
return (
17+
<Dialog
18+
className={cn(
19+
'container p-0',
20+
'max-w-[calc(100vw-2rem)] sm:max-w-[512px] md:max-w-[640px] lg:max-w-[640px]'
21+
)}
22+
>
23+
<Card className="rounded-lg border bg-background shadow-sm">
24+
<CardHeader className="border-b px-6 py-4">
25+
<h2>Do you really want to delete this information?</h2>
26+
</CardHeader>
27+
<CardFooter className="flex justify-end gap-2 p-6">
28+
<Button
29+
variant="outline"
30+
className="p-1"
31+
onClick={() => window.history.back()}
32+
>
33+
Cancel
34+
</Button>
35+
<Button
36+
variant="primary"
37+
className="p-1"
38+
onClick={async () => {
39+
const result = await deleteFacultySelectionElement(topic, id);
40+
toast({
41+
title: result.success ? 'Deleted' : 'Error',
42+
description: result.message,
43+
variant: result.success ? 'success' : 'error',
44+
});
45+
window.history.back();
46+
}}
47+
>
48+
Delete
49+
</Button>
50+
</CardFooter>
51+
</Card>
52+
</Dialog>
53+
);
54+
}
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
'use client';
2+
import { zodResolver } from '@hookform/resolvers/zod';
3+
import { type Control, type FieldPath, useForm } from 'react-hook-form';
4+
import type z from 'zod';
5+
import { ZodFirstPartyTypeKind, type ZodSchema } from 'zod';
6+
7+
import { Button } from '~/components/buttons';
8+
import { Input } from '~/components/inputs';
9+
import {
10+
Select,
11+
SelectContent,
12+
SelectItem,
13+
SelectTrigger,
14+
SelectValue,
15+
} from '~/components/inputs/select';
16+
import { CardContent, CardFooter } from '~/components/ui';
17+
import {
18+
FormControl,
19+
FormField,
20+
FormItem,
21+
FormLabel,
22+
FormMessage,
23+
FormProvider,
24+
} from '~/components/ui/form';
25+
import { toast } from '~/lib/hooks';
26+
import {
27+
facultyPersonalDetailsSchema,
28+
facultyProfileSchemas,
29+
} from '~/lib/schemas/faculty-profile';
30+
import {
31+
editFacultyProfilePersonalDetails,
32+
upsertFacultySection,
33+
} from '~/server/actions/faculty-profile';
34+
35+
export function FacultyForm({
36+
locale,
37+
topic,
38+
id,
39+
existingDetails,
40+
}: {
41+
locale: string;
42+
topic: string;
43+
id?: number;
44+
existingDetails?: z.infer<
45+
(typeof facultyProfileSchemas)[keyof typeof facultyProfileSchemas]
46+
> | null;
47+
}) {
48+
const schema =
49+
facultyProfileSchemas[topic as keyof typeof facultyProfileSchemas];
50+
51+
const form = useForm<z.infer<typeof schema>>({
52+
resolver: zodResolver(schema),
53+
defaultValues:
54+
existingDetails ??
55+
(Object.keys(schema.shape).reduce(
56+
(acc, key) => {
57+
acc[key as keyof typeof schema.shape] = undefined;
58+
return acc;
59+
},
60+
{} as Record<string, unknown>
61+
) as z.infer<typeof schema>),
62+
});
63+
64+
const onSubmit = async (values: z.infer<typeof schema>) => {
65+
const result = await upsertFacultySection(topic, id ?? null, values);
66+
toast({
67+
title: result.success ? 'Success' : 'Error',
68+
description: result.success
69+
? `Successfully ${id ? 'updated' : 'created'} ${topic} details.`
70+
: result.message,
71+
variant: result.success ? 'success' : 'error',
72+
});
73+
window.history.go(-1);
74+
};
75+
76+
return (
77+
<FormProvider {...form}>
78+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
79+
<CardContent className="grid gap-4 p-6 md:grid-cols-2">
80+
{renderFields(form.control, schema.shape)}
81+
</CardContent>
82+
<CardFooter className="flex justify-end border-t pt-4 ">
83+
<Button
84+
type="button"
85+
variant="secondary"
86+
className="mr-2 p-1"
87+
onClick={() => window.history.back()}
88+
>
89+
Cancel
90+
</Button>
91+
<Button type="submit" variant="primary" className="p-1">
92+
{id ? 'Update' : 'Create'}
93+
</Button>
94+
</CardFooter>
95+
</form>
96+
</FormProvider>
97+
);
98+
}
99+
100+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
101+
const renderFields = <T extends Record<string, any>>(
102+
formControl: Control<T>,
103+
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
104+
schemaShape: Record<string, ZodSchema<unknown> | never>
105+
) => {
106+
const renderField = (fieldName: string) => {
107+
const fieldSchema = schemaShape[fieldName] as
108+
| z.ZodOptional<z.ZodString>
109+
| z.ZodString
110+
| z.ZodNumber
111+
| z.ZodDate
112+
| z.ZodEnum<[string, ...string[]]>
113+
| z.ZodEffects<z.ZodUnion<[z.ZodString, z.ZodDate]>, Date, string | Date>
114+
| z.ZodEffects<
115+
z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodOptional<z.ZodDate>]>,
116+
Date | undefined,
117+
string | Date | undefined
118+
>
119+
| z.ZodUnion<[z.ZodDate, z.ZodString]>;
120+
const isOptional = fieldSchema.isOptional();
121+
122+
// Generate appropriate label from field name
123+
const label = fieldName
124+
.replace(/([A-Z])/g, ' $1')
125+
.replace(/^./, (str: string) => str.toUpperCase())
126+
.trim();
127+
128+
// Check if it's an enum field
129+
if (fieldSchema._def.typeName === ZodFirstPartyTypeKind.ZodEnum) {
130+
const enumValues = fieldSchema._def.values;
131+
return (
132+
<FormField
133+
key={fieldName}
134+
control={formControl}
135+
name={fieldName as unknown as FieldPath<T>}
136+
render={({ field }) => (
137+
<FormItem>
138+
<FormLabel>{label}</FormLabel>
139+
<FormControl>
140+
<Select
141+
value={field.value}
142+
onValueChange={field.onChange}
143+
variant="form"
144+
>
145+
<SelectTrigger className="w-full">
146+
<SelectValue placeholder={`Select ${label}`} />
147+
</SelectTrigger>
148+
<SelectContent className="z-elevated">
149+
{enumValues.map((value: string) => (
150+
<SelectItem key={value} value={value}>
151+
{value.charAt(0).toUpperCase() + value.slice(1)}
152+
</SelectItem>
153+
))}
154+
</SelectContent>
155+
</Select>
156+
</FormControl>
157+
<FormMessage />
158+
</FormItem>
159+
)}
160+
/>
161+
);
162+
}
163+
164+
const isDateField =
165+
fieldSchema._def.typeName === ZodFirstPartyTypeKind.ZodDate ||
166+
(fieldSchema._def.typeName === ZodFirstPartyTypeKind.ZodUnion &&
167+
Array.isArray(fieldSchema._def.options) &&
168+
fieldSchema._def.options.some(
169+
(option) =>
170+
option._def?.typeName === ZodFirstPartyTypeKind.ZodDate ||
171+
option._def?.typeName === ZodFirstPartyTypeKind.ZodString
172+
)) ||
173+
(fieldSchema._def.typeName == ZodFirstPartyTypeKind.ZodEffects &&
174+
fieldSchema._def.schema._def.typeName ===
175+
ZodFirstPartyTypeKind.ZodUnion &&
176+
Array.isArray(fieldSchema._def.schema._def.options) &&
177+
fieldSchema._def.schema._def.options.some(
178+
(option) =>
179+
(option._def?.typeName === ZodFirstPartyTypeKind.ZodOptional &&
180+
option._def?.innerType._def.typeName ===
181+
ZodFirstPartyTypeKind.ZodDate) ||
182+
option._def?.typeName === ZodFirstPartyTypeKind.ZodDate
183+
)) ||
184+
fieldName.toLowerCase().includes('date');
185+
const isNumberField =
186+
fieldSchema._def.typeName === ZodFirstPartyTypeKind.ZodNumber;
187+
188+
// Default to text input
189+
return (
190+
<FormField
191+
key={fieldName}
192+
control={formControl}
193+
name={fieldName as unknown as FieldPath<T>}
194+
render={({ field }) => (
195+
<FormItem>
196+
<FormControl>
197+
<Input
198+
id={fieldName}
199+
label={label}
200+
type={isDateField ? 'date' : isNumberField ? 'number' : 'text'}
201+
required={!isOptional}
202+
{...field}
203+
/>
204+
</FormControl>
205+
<FormMessage />
206+
</FormItem>
207+
)}
208+
/>
209+
);
210+
};
211+
212+
return Object.keys(schemaShape).map((fieldName) => {
213+
// Make certain fields span full width
214+
const isFullWidth = ['description', 'about', 'title', 'people'].includes(
215+
fieldName.toLowerCase()
216+
);
217+
218+
return (
219+
<div key={fieldName} className={isFullWidth ? 'md:col-span-2' : ''}>
220+
{renderField(fieldName)}
221+
</div>
222+
);
223+
});
224+
};
225+
226+
export function FacultyPersonalDetailsForm({
227+
locale,
228+
existingDetails,
229+
}: {
230+
locale: string;
231+
existingDetails: z.infer<typeof facultyPersonalDetailsSchema>;
232+
}) {
233+
const form = useForm<z.infer<typeof facultyPersonalDetailsSchema>>({
234+
resolver: zodResolver(facultyPersonalDetailsSchema),
235+
defaultValues: existingDetails,
236+
});
237+
238+
const onSubmit = async (
239+
values: z.infer<typeof facultyPersonalDetailsSchema>
240+
) => {
241+
const result = await editFacultyProfilePersonalDetails(values);
242+
toast({
243+
title: result.success ? 'Success' : 'Error',
244+
description: result.success
245+
? `Successfully updated personal details.`
246+
: result.message,
247+
variant: result.success ? 'success' : 'error',
248+
});
249+
window.history.go(-1);
250+
};
251+
252+
return (
253+
<FormProvider {...form}>
254+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
255+
<CardContent className="grid gap-4 p-6 md:grid-cols-2">
256+
{renderFields(form.control, facultyPersonalDetailsSchema.shape)}
257+
</CardContent>
258+
<CardFooter className="flex justify-end border-t pt-4 ">
259+
<Button
260+
type="button"
261+
variant="secondary"
262+
className="mr-2 p-1"
263+
onClick={() => window.history.back()}
264+
>
265+
Cancel
266+
</Button>
267+
<Button type="submit" variant="primary" className="p-1">
268+
{'Update'}
269+
</Button>
270+
</CardFooter>
271+
</form>
272+
</FormProvider>
273+
);
274+
}

0 commit comments

Comments
 (0)