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

Chaitya/admin dropdown new #273

Merged
merged 15 commits into from
Jan 17, 2025
Merged
66 changes: 63 additions & 3 deletions src/components/common/VerticalFormItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HTMLInputTypeAttribute, ReactNode } from 'react';
import { UseFormRegisterReturn } from 'react-hook-form';
import React, { HTMLInputTypeAttribute, ReactNode, useState } from 'react';
import { UseFormRegisterReturn, useForm } from 'react-hook-form';
import { AiOutlineSearch } from 'react-icons/ai';
import styles from './style.module.scss';

interface InputTypeProps {
Expand All @@ -11,6 +12,10 @@ interface SelectTypeProps {
element: 'select';
options: (number | string)[];
}
interface SelectMultipleTypeProps {
element: 'select-multiple';
options: string[];
}
interface FormItemProps {
icon: ReactNode;
name: string;
Expand All @@ -20,10 +25,14 @@ interface FormItemProps {
inputHeight?: string;
}

type VerticalFormProps = FormItemProps & (InputTypeProps | SelectTypeProps);
type VerticalFormProps = FormItemProps &
(InputTypeProps | SelectTypeProps | SelectMultipleTypeProps);

const VerticalFormItem = (props: VerticalFormProps) => {
const { icon, placeholder, formRegister, element, error, inputHeight } = props;
const { setValue } = useForm();
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
const [searchTerm, setSearchTerm] = useState('');

if (element === 'input') {
const { type } = props;
Expand Down Expand Up @@ -61,6 +70,9 @@ const VerticalFormItem = (props: VerticalFormProps) => {
lineHeight: inputHeight,
}}
>
<option value="" disabled selected>
{placeholder}
</option>
{options.map(value => (
<option key={value}>{value}</option>
))}
Expand All @@ -71,6 +83,54 @@ const VerticalFormItem = (props: VerticalFormProps) => {
);
}

if (element === 'select-multiple') {
const { options } = props;
const handleSelectionChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const selectedValues = Array.from(event.target.selectedOptions, option => option.value);
setSelectedOptions(selectedValues);
setValue('email', selectedValues, { shouldValidate: true });
};
const filteredOptions = options.filter(option =>
option.toLowerCase().includes(searchTerm.toLowerCase())
);

return (
<div className={styles.formItem}>
<div className={styles.formInput}>
<div className={styles.iconContainer}>
<AiOutlineSearch />
</div>
<div>
<h1>Selected: {selectedOptions.join(', ')}</h1>
<div>
<input
type="text"
placeholder="Click to search"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
className={styles.inputField}
/>
<select
className={styles.selectMultipleField}
style={{
lineHeight: inputHeight,
}}
multiple
{...formRegister}
onChange={handleSelectionChange}
>
{filteredOptions.map(value => (
<option key={value}>{value}</option>
))}
</select>
</div>
</div>
</div>
<p className={styles.formError}>{error?.message}</p>
</div>
);
}

return null;
};

Expand Down
38 changes: 38 additions & 0 deletions src/components/common/VerticalFormItem/style.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,44 @@
display: none;
}
}

.selectMultipleField {
align-items: center;
appearance: none;
background-color: var(--theme-background);
border: 0;
border-radius: 0.25em;
cursor: pointer;
font-family: inherit;
font-size: 1em;
height: auto; /* Adjust height to match your dropdown */
line-height: 1.75rem;
margin: 0;
outline: none;
// overflow-y: auto; /* Allow scrolling if there are many options */
padding: 2px;
transition: 0.3s ease;
width: 100%;

// &::-ms-expand {
// display: none;
// }
}

// .selectedOptions {
// align-items: center;
// appearance: none;
// background-color: var(--theme-background);
// border: 0;
// border-radius: 0.25em;
// font-family: inherit;
// font-size: 1em;
// height: auto; /* Adjust height to match your dropdown */
// line-height: 1.75rem;
// margin: 0;
// outline: none;
// width: 100%;
// }
Chaitya2108 marked this conversation as resolved.
Show resolved Hide resolved
}

.formError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type Styles = {
iconContainer: string;
inputField: string;
selectField: string;
selectMultipleField: string;
};

export type ClassNames = keyof Styles;
Expand Down
105 changes: 103 additions & 2 deletions src/lib/api/AdminAPI.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { config } from '@/lib';
import type { ModifyUserAccessLevelResponse, PrivateProfile } from '@/lib/types/apiResponses';
import { Bonus, Milestone, SubmitAttendanceForUsersRequest } from '@/lib/types/apiRequests';
import { UUID } from '@/lib/types';
import {
CreateBonusResponse,
GetAllNamesAndEmailsResponse,
SubmitAttendanceForUsersResponse,
type ModifyUserAccessLevelResponse,
type PrivateProfile,
type NameAndEmail,
CreateMilestoneResponse,
} from '@/lib/types/apiResponses';
import axios from 'axios';

/**
Expand Down Expand Up @@ -31,4 +41,95 @@ export const manageUserAccess = async (
return response.data.updatedUsers;
};

export default manageUserAccess;
/**
* Get all users emails
*/
export const getAllUserEmails = async (token: string): Promise<NameAndEmail[]> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.admin.emails}`;

const response = await axios.get<GetAllNamesAndEmailsResponse>(requestUrl, {
headers: {
Authorization: `Bearer ${token}`,
},
});

return response.data.namesAndEmails;
};

/**
* Retroactively add bonus points
* @param token Bearer token
* @param users Users that the bonus is going to
* @param description Description for the bonus
* @param points Number of points awarded
* @returns Updated users
*/
export const addBonus = async (
token: string,
users: string[],
description: string,
points: number
): Promise<CreateBonusResponse> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.admin.bonus}`;
const bonus: Bonus = { users: users, description: description, points: points };
const response = await axios.post<CreateBonusResponse>(
requestUrl,
{ bonus: bonus },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
return response.data;
};

/**
* Retroactively add attendance points
* @param token Bearer token
* @param users List of users
* @param event Event for whcih attendance is added
* @param points Number of points awarded
*/
export const addAttendance = async (
token: string,
users: string[],
event: UUID
): Promise<SubmitAttendanceForUsersResponse> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.admin.attendance}`;
const attendance: SubmitAttendanceForUsersRequest = {
users: users,
event: event,
};

const response = await axios.post<SubmitAttendanceForUsersResponse>(requestUrl, attendance, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
};

export const createMilestone = async (
token: string,
name: string,
points: number
): Promise<CreateMilestoneResponse> => {
const requestUrl = `${config.api.baseUrl}${config.api.endpoints.admin.milestone}`;
const milestone: Milestone = {
name: name,
points: points,
};

const response = await axios.post<CreateMilestoneResponse>(
requestUrl,
{ milestone },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);

return response.data;
};
1 change: 1 addition & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const config = {
bonus: '/admin/bonus',
emails: '/admin/email',
access: '/admin/access',
milestone: '/admin/milestone',
},
event: {
event: '/event',
Expand Down
13 changes: 13 additions & 0 deletions src/lib/types/apiResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export interface CreateBonusResponse extends ApiResponse {
emails: string[];
}

export interface AddAttendanceResponse extends ApiResponse {
emails: string[];
}

export interface UploadBannerResponse extends ApiResponse {
banner: string;
}
Expand All @@ -50,6 +54,15 @@ export interface GetAllEmailsResponse extends ApiResponse {
emails: string[];
}

export interface NameAndEmail {
firstName: string;
lastName: string;
email: string;
}
export interface GetAllNamesAndEmailsResponse extends ApiResponse {
namesAndEmails: NameAndEmail[];
}

export interface SubmitAttendanceForUsersResponse extends ApiResponse {
attendances: PublicAttendance[];
}
Expand Down
Loading
Loading