Skip to content
Open
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
88 changes: 81 additions & 7 deletions actions/admin/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import {
PostRequest,
PatchRequest,
} from '@/base-api/method';
import { inviteMentorProps } from '@/types/requests';
import type { inviteMentorProps } from '@/types/requests';
import type { getStartedMentorFormValues } from '@/types/get-started';
import { ToggleMentorStatusProps } from '@/types/mentor';
import { CreateOrganizationProps } from '@/types/admin';

import { revalidateTag } from 'next/cache';
const Url = {
adminAccount: `admin/account`,
adminMentors: `admin/mentor`,
mentorProfile: `mentor/profile`,
inviteAdmin: `admin/user`,
};

export const checkAccount = async (params: string) => {
Expand All @@ -21,17 +27,16 @@ export const checkAccount = async (params: string) => {
return data;
};

export const createOrganazation = async (
id: string,
body: { name: string; domain: string }
) => {
export const createOrganazation = async ({
id,
body,
}: CreateOrganizationProps) => {
const putRequest = new PatchRequest(
`${Url.adminAccount}/${id}`,
'createOrg',
body
);
const data = await putRequest.putData();
return data;
return await putRequest.patchData();
};

export const deleteMentor = async (id: string) => {
Expand Down Expand Up @@ -59,6 +64,75 @@ export const inviteMentore = async (body: inviteMentorProps) => {
return data;
};

export const submitMentorForm = async (
formData: getStartedMentorFormValues,
accountId: string
) => {
// Transform data to match backend format
const backendData = {
expertise: formData.expertise.reduce(
(acc, exp) => {
const expertiseMap: Record<string, string> = {
marriageCounseling: 'Expert in marriage counseling',
discipleship: 'Expert in discipleship counseling',
spritual: 'Expert in spiritual counseling',
dayToDay: 'Expert in day-to-day counseling',
lifeCoach: 'Expert in life coaching',
psychology: 'Expert in psychological counseling',
};
const key =
exp
.replace(/Counseling$/, 'Counselor')
.replace(/([A-Z])/g, ' $1')
.trim()
.toLowerCase() + 'Counselor';
acc[key] = expertiseMap[exp] || `Expert in ${exp}`;
return acc;
},
{} as Record<string, string>
),
capacity: formData.capacity,
availability: Object.entries(formData.availability)
.filter(([_, value]) => value !== undefined)
.reduce(
(acc, [day, value]) => {
if (!value) return acc;

const formattedDay = day.charAt(0).toUpperCase() + day.slice(1);
const startTime = value.startTime;
const endTime = value.endTime;
const timeRange = `${startTime.hour}:${startTime.minute} ${startTime.dayPeriod} - ${endTime.hour}:${endTime.minute} ${endTime.dayPeriod}`;

acc[formattedDay] = [timeRange];
return acc;
},
{} as Record<string, string[]>
),
age: formData.age,
gender: formData.gender.toUpperCase(),
location: formData.location,
};

const url = `${Url.mentorProfile}?accountId=${accountId}`;

const patchRequest = new PatchRequest(url, 'submit-mentor-form', backendData);
return await patchRequest.patchData();
};

export const toggleMentorStatus = async ({
mentorId,
accountId,
isActive,
}: ToggleMentorStatusProps) => {
const url = `${Url.inviteAdmin}/${mentorId}/activate/${accountId}`;
const patchRequest = new PatchRequest(url, 'toggle-mentor-status', {
isActive,
});
const data = await patchRequest.patchData();
revalidateTag('admin-mentors');
return data;
};

export async function revalidateWithLogging(tag: string) {
return revalidateTag(tag);
}
15 changes: 13 additions & 2 deletions base-api/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,23 @@ class PostRequest extends ApiRequest {
}
}

// PUT request subclass
// PATCH request subclass
class PatchRequest extends ApiRequest {
constructor(url: string, tag: string, data: unknown) {
super(url, tag, data, 'PATCH');
}

public async patchData() {
return this.executeRequest();
}
}

// PUT request subclass
class PutRequest extends ApiRequest {
constructor(url: string, tag: string, data: unknown) {
super(url, tag, data, 'PUT');
}

public async putData() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why you change the put to patch , instead of adding it? for such case we don't change such function , if we need additional we have to add as new.

return this.executeRequest();
}
Expand All @@ -70,4 +81,4 @@ class DeleteRequest extends ApiRequest {
}
}

export { GetRequest, PostRequest, PatchRequest, DeleteRequest };
export { GetRequest, PostRequest, PatchRequest, PutRequest, DeleteRequest };
81 changes: 68 additions & 13 deletions components/shared/admin/admin-mentors/mentors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import type { Column, FilterOption } from '@/types/data-table';
import { toast } from '@/hooks/use-toast';
import { InviteMentorDialog } from './invite-mentor-dialog';
import { endPoints } from '@/data/end-points';
import { deleteMentor } from '@/actions/admin/admin';
import { deleteMentor, toggleMentorStatus } from '@/actions/admin/admin';
import type { Account } from '@/types/users';
import { userProfile } from '@/actions/auth/login';
import { Mentor } from '@/types/mentor';
import { Switch } from '@/components/ui/switch';

const columns: Column<Mentor>[] = [
const createColumns = (
client: Account | null,
setTriggerState: React.Dispatch<React.SetStateAction<boolean>>
): Column<Mentor>[] => [
{
key: 'name',
header: 'Name',
Expand All @@ -30,41 +34,91 @@ const columns: Column<Mentor>[] = [
{
key: 'age',
header: 'Age',
render: (mentor: Mentor) => (mentor.age === null ? 'null' : mentor.age),
render: (mentor: Mentor) => (mentor.age === null ? 'N/A' : mentor.age),
},
{ key: 'gender', header: 'Gender' },
{
key: 'expertise',
header: 'Expertise',
render: (mentor: Mentor) =>
mentor.expertise === null ? 'null' : mentor.expertise,
mentor.expertise
? Object.entries(mentor.expertise)
.map(([key, description]) => `${key}: ${description}`)
.join(', ')
: 'N/A',
},
{
key: 'availability',
header: 'Availability',
render: (mentor: Mentor) =>
mentor.availability === null
? 'null'
: mentor.availability?.startDate || 'null',
mentor.availability
? Object.entries(mentor.availability)
.map(([day, times]) => `${day}: ${times.join(', ')}`)
.join('; ')
: 'N/A',
},
{
key: 'capacity',
header: 'Capacity',
render: (mentor: Mentor) =>
mentor.capacity === null ? 'N/A' : mentor.capacity,
},
{
key: 'location',
header: 'Location',
render: (mentor: Mentor) =>
mentor.location === null ? 'null' : mentor.location,
render: (mentor: Mentor) => (mentor.location ? mentor.location : 'N/A'),
},
{
key: 'isActive',
header: 'Status',
render: (mentor: Mentor) => (
<Badge variant="secondary">{mentor.isActive ? 'Yes' : 'No'}</Badge>
<div className="flex items-center gap-2">
<Switch
checked={mentor.isActive}
onCheckedChange={async (checked) => {
try {
if (!client?.id) {
throw new Error('User account ID missing');
}

await toggleMentorStatus({
mentorId: String(mentor.id),
accountId: client.id,
isActive: checked,
});

mentor.isActive = checked;
setTriggerState((prev) => !prev);

toast({
variant: 'success',
title: 'Status Updated!',
description: `Mentor status has been ${checked ? 'activated' : 'deactivated'}.`,
});
} catch (error) {
console.error('Status toggle error:', error);
toast({
variant: 'destructive',
title: 'Error!',
description:
error instanceof Error
? error.message
: 'Failed to update mentor status',
});
}
}}
/>
<Badge variant={mentor.isActive ? 'default' : 'secondary'}>
{mentor.isActive ? 'Active' : 'Inactive'}
</Badge>
</div>
),
},
];

const filterOptions: FilterOption<Mentor>[] = [
{ key: 'isActive', label: 'Yes' },
{ key: 'isActive', label: 'No' },
{ key: 'gender', label: 'FEMALE' },
{ key: 'gender', label: 'MALE' },
];

const searchFields: (keyof Mentor)[] = ['name', 'email', 'gender', 'isActive'];
Expand All @@ -81,6 +135,7 @@ const MentorsTable: React.FC = () => {
};
fetchUserProfile();
}, []);

const endPoint = `${endPoints.adminMentors}?accountId=${clientUser?.id}`;
const [itemsPerPage, onItemsPerPageChange] = useState<number>(10);

Expand Down Expand Up @@ -130,7 +185,7 @@ const MentorsTable: React.FC = () => {
<DataTable<Mentor>
tag="admin-mentors"
apiUrl={endPoint}
columns={columns}
columns={createColumns(clientUser, setTriggerState)}
searchFields={searchFields}
filterOptions={filterOptions}
itemsPerPage={itemsPerPage}
Expand Down
14 changes: 1 addition & 13 deletions components/shared/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ import {
DropdownMenuTrigger,
} from '@radix-ui/react-dropdown-menu';

// ... other imports

export interface Column<T> {
key: keyof T;
header: string;
Expand Down Expand Up @@ -99,7 +97,7 @@ const DataTable = <T extends { id: string | number }>({

if (response && response.data) {
setData(response.data);
setTotalPages(response.meta.totalPages); // Use meta.totalPages for pagination
setTotalPages(response.meta.totalPages);
} else {
throw new Error('Invalid response format');
}
Expand All @@ -120,14 +118,11 @@ const DataTable = <T extends { id: string | number }>({
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};
// Apply search and filters to the data
const filteredData = data.filter((item) => {
// Apply search
const matchesSearch = searchFields.some((field) =>
item[field]?.toString().toLowerCase().includes(searchTerm.toLowerCase())
);

// Apply filters
const matchesFilters = filters.every((filter) => {
const [key, value] = filter.split(':');
return item[key as keyof T]?.toString() === value;
Expand All @@ -136,13 +131,6 @@ const DataTable = <T extends { id: string | number }>({
return matchesSearch && matchesFilters;
});

// Filter data based on search term
// const filteredData = data.filter((item) =>
// searchFields.some((field) =>
// item[field]?.toString().toLowerCase().includes(searchTerm.toLowerCase())
// )
// );

const handleDelete = (id: string | number) => {
setDeleteDialog({ open: true, id });
};
Expand Down
37 changes: 37 additions & 0 deletions components/shared/get-started/fields/capacity-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';

import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import type { AgeFieldProps } from '@/types/get-started';

export const CapacityField = ({ control, className }: AgeFieldProps) => {
return (
<FormField
control={control}
name="capacity"
render={({ field }) => (
<FormItem className={className}>
<FormLabel>Capacity</FormLabel>
<FormControl>
<Input
placeholder="Enter your capacity"
type="number"
{...field}
onChange={(e) => {
const value = e.target.value;
field.onChange(value);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
};
8 changes: 5 additions & 3 deletions components/shared/get-started/fields/gender-field.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import {
FormField,
FormItem,
Expand All @@ -13,13 +15,13 @@ export function GenderField({ control, options, className }: GenderFieldProps) {
return (
<FormField
control={control}
name="Gender"
name="gender"
render={({ field }) => (
<FormItem className="">
<FormItem className={className}>
<FormLabel className="text-lg font-semibold">Gender</FormLabel>
<FormControl>
<RadioGroup
defaultValue="male"
value={field.value}
onValueChange={field.onChange}
className="flex gap-10"
>
Expand Down
Loading