Skip to content

PM-579 QR feedback on copilot request form #999

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

Merged
merged 2 commits into from
Feb 12, 2025
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
154 changes: 90 additions & 64 deletions src/apps/copilots/src/pages/copilot-request-form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { FC, useContext, useState } from 'react'
import { SWRResponse } from 'swr'
import { FC, useContext, useMemo, useState } from 'react'
import { bind, isEmpty } from 'lodash'
import { toast } from 'react-toastify'
import classNames from 'classnames'

import { profileContext, ProfileContextData } from '~/libs/core'
import { Button, IconSolid, InputDatePicker, InputMultiselectOption,
InputRadio, InputSelect, InputText, InputTextarea } from '~/libs/ui'
InputRadio, InputSelect, InputSelectOption, InputSelectReact, InputText, InputTextarea } from '~/libs/ui'
import { InputSkillSelector } from '~/libs/shared'

import { saveCopilotRequest, useFetchProjects } from '../../services/projects'
Expand All @@ -21,16 +20,19 @@ const CopilotRequestForm: FC<{}> = () => {
const [formValues, setFormValues] = useState<any>({})
const [isFormChanged, setIsFormChanged] = useState(false)
const [formErrors, setFormErrors] = useState<any>({})
const { data: projectsData }: SWRResponse<Project[], any> = useFetchProjects()
const [searchTerm, setSearchTerm] = useState<string>('')
const { data: projectsData }: { data?: Project[] } = useFetchProjects(searchTerm)
const [existingCopilot, setExistingCopilot] = useState<string>('')
const [paymentType, setPaymentType] = useState<string>('')

const projects = projectsData
? projectsData.map(project => ({
label: project.name,
value: project.id,
}))
: []
const projects = useMemo(
() => (
projectsData
? projectsData.map(project => ({ label: project.name, value: project.id }))
: []
),
[projectsData],
)

const projectTypes = ProjectTypes ? ProjectTypes.map(project => ({
label: project,
Expand All @@ -56,6 +58,27 @@ const CopilotRequestForm: FC<{}> = () => {
setPaymentType(t)
}

function filterProjects(option: InputSelectOption, value: string): boolean {
setSearchTerm(value)
return (
option.label
?.toString()
.toLowerCase()
.includes(value.toLowerCase()) ?? false
)
}

function handleProjectSearch(inputValue: string): void {
setSearchTerm(inputValue)
}

function handleProjectSelect(option: React.ChangeEvent<HTMLInputElement>): void {
setFormValues((prevValues: any) => ({
...prevValues,
projectId: option.target.value,
}))
}

function handleFormValueChange(
key: string,
event: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>,
Expand Down Expand Up @@ -121,59 +144,61 @@ const CopilotRequestForm: FC<{}> = () => {
function handleFormAction(): void {
const updatedFormErrors: { [key: string]: string } = {}

if (!formValues.projectId) {
updatedFormErrors.projectId = 'Project is required'
}

if (!existingCopilot) {
updatedFormErrors.existingCopilot = 'Selection is required'
}

if (!formValues.complexity) {
updatedFormErrors.complexity = 'Selection is required'
}

if (!formValues.requiresCommunication) {
updatedFormErrors.requiresCommunication = 'Selection is required'
}

if (!formValues.paymentType) {
updatedFormErrors.paymentType = 'Selection is required'
}

if (!formValues.projectType) {
updatedFormErrors.projectType = 'Selecting project type is required'
}

if (!formValues.overview) {
updatedFormErrors.overview = 'Providing a project overview is required'
}

if (!formValues.skills) {
updatedFormErrors.skills = 'Providing skills is required'
}

if (!formValues.startDate) {
updatedFormErrors.startDate = 'Providing a start date for copilot is required'
}

if (!formValues.numWeeks) {
updatedFormErrors.numWeeks = 'Providing number of weeks is required'
}

if (!formValues.tzRestrictions) {
updatedFormErrors.tzRestrictions = 'Providing timezone restrictions is required. Type No if no restrictions'
}

if (!formValues.numHoursPerWeek) {
updatedFormErrors.numHoursPerWeek = 'Providing commitment per week is required'
}
const fieldValidations: { condition: boolean; key: string; message: string }[] = [
{ condition: !formValues.projectId, key: 'projectId', message: 'Project is required' },
{ condition: !existingCopilot, key: 'existingCopilot', message: 'Selection is required' },
{ condition: !formValues.complexity, key: 'complexity', message: 'Selection is required' },
{
condition: !formValues.requiresCommunication,
key: 'requiresCommunication',
message: 'Selection is required',
},
{ condition: !formValues.paymentType, key: 'paymentType', message: 'Selection is required' },
{ condition: !formValues.projectType, key: 'projectType', message: 'Selecting project type is required' },
{
condition: !formValues.overview || formValues.overview.length < 10,
key: 'overview',
message: 'Project overview must be at least 10 characters',
},
{
condition: !formValues.skills || formValues.skills.length === 0,
key: 'skills',
message: 'Providing skills is required',
},
{
condition: !formValues.startDate,
key: 'startDate',
message: 'Providing a start date for copilot is required',
},
{
condition: !formValues.numWeeks,
key: 'numWeeks',
message: 'Providing number of weeks is required',
},
{
condition: !formValues.tzRestrictions,
key: 'tzRestrictions',
message: 'Providing timezone restrictions is required. Type No if no restrictions',
},
{
condition: !formValues.numHoursPerWeek,
key: 'numHoursPerWeek',
message: 'Providing commitment per week is required',
},
]

fieldValidations.forEach(
({ condition, key, message }: { condition: boolean; key: string; message: string }) => {
if (condition) {
updatedFormErrors[key] = message
}
},
)

if (isEmpty(updatedFormErrors)) {
saveCopilotRequest(formValues)
.then(() => {
toast.success('Copilot request sent successfully')
// Reset form after successful submission
setFormValues({
complexity: '',
copilotUsername: '',
Expand Down Expand Up @@ -208,22 +233,24 @@ const CopilotRequestForm: FC<{}> = () => {
<form>
<h1 className={styles.heading}> Copilot Request </h1>
<p className={styles.subheading}>
{' '}
Hi,
{profile?.firstName}
{' '}
!
This form is to request a copilot for your project. Please fill in the details below.
</p>
<p className={styles.formRow}>Select the project you want the copilot for</p>
<InputSelect
<InputSelectReact
tabIndex={0}
options={projects}
value={formValues.projectId}
onChange={bind(handleFormValueChange, this, 'projectId')}
onChange={handleProjectSelect}
onInputChange={handleProjectSearch}
name='project'
label='Project'
placeholder='Select the project you wish to request a copilot for'
placeholder='Start typing the name of the project'
dirty
filterOption={filterProjects}
error={formErrors.projectId}
/>
<p className={styles.formRow}>
Expand Down Expand Up @@ -331,7 +358,6 @@ const CopilotRequestForm: FC<{}> = () => {
customRadius
noCaps
leftAlignText
textWrap
/>
{formErrors.complexity && (
<p className={styles.error}>
Expand Down Expand Up @@ -443,7 +469,7 @@ const CopilotRequestForm: FC<{}> = () => {
{formErrors.requiresCommunication && (
<p className={styles.error}>
<IconSolid.ExclamationIcon />
{formErrors.requiresCommunicatn}
{formErrors.requiresCommunication}
</p>
)}
<p className={styles.formRow}>Will this role be standard payments or something else?</p>
Expand Down
6 changes: 3 additions & 3 deletions src/apps/copilots/src/services/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { CopilotRequest } from '../models/CopilotRequest'

const baseUrl = `${EnvironmentConfig.API.V5}/projects`

export const useFetchProjects = (): SWRResponse<Project[]> => {
const response = useSWR(baseUrl, xhrGetAsync<Project[]>, {
export const useFetchProjects = (search: string): SWRResponse<Project[], any> => {
const url = `${baseUrl}?name=${search}`
return useSWR(url, xhrGetAsync<Project[]>, {
refreshInterval: 0,
revalidateOnFocus: false,
})
return response
}

export const saveCopilotRequest = (request: CopilotRequest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface InputSelectReactProps {
readonly label?: string
readonly name: string
readonly onChange: (event: ChangeEvent<HTMLInputElement>) => void
readonly onInputChange?: (newValue: string) => void
readonly options: OptionsOrGroups<unknown, GroupBase<unknown>>
readonly placeholder?: string
readonly tabIndex?: number
Expand Down Expand Up @@ -138,6 +139,7 @@ const InputSelectReact: FC<InputSelectReactProps> = props => {
)
}
onChange={handleSelect}
onInputChange={props.onInputChange}
menuPortalTarget={menuPortalTarget}
classNamePrefix={styles.sel}
tabIndex={props.tabIndex}
Expand Down