From 981d217f7827dea469a02e1112bffa2b2116365b Mon Sep 17 00:00:00 2001 From: SebassNoob Date: Thu, 11 Jan 2024 23:58:02 +0800 Subject: [PATCH] refactor: organise service box and forms --- interapp-frontend/src/api/api_client.ts | 7 +- .../src/app/announcements/page.tsx | 55 ++++----- .../app/services/AddService/AddService.tsx | 28 +++-- .../app/services/EditService/EditService.tsx | 4 - .../app/services/ServiceBox/ServiceBox.tsx | 65 ++++------- .../src/app/services/ServiceBox/styles.css | 29 +++-- .../ServiceBoxUsers/ServiceBoxUsers.tsx | 89 ++++++++++----- interapp-frontend/src/app/services/page.tsx | 9 +- interapp-frontend/src/app/services/styles.css | 2 +- .../PillsInputWithSearch.tsx | 108 ------------------ 10 files changed, 149 insertions(+), 247 deletions(-) delete mode 100644 interapp-frontend/src/components/PillsInputWithSearch/PillsInputWithSearch.tsx diff --git a/interapp-frontend/src/api/api_client.ts b/interapp-frontend/src/api/api_client.ts index faa27af7..7135fa15 100644 --- a/interapp-frontend/src/api/api_client.ts +++ b/interapp-frontend/src/api/api_client.ts @@ -26,11 +26,8 @@ export class APIClient { : `http://${process.env.BACKEND_HOST}:${process.env.BACKEND_PORT}/api`, }); this.instance.interceptors.request.use((req) => { - - if (this.config.useMultiPart) - req.headers['Content-Type'] = 'multipart/form-data'; - else - req.headers['Content-Type'] = 'application/json'; + if (this.config.useMultiPart) req.headers['Content-Type'] = 'multipart/form-data'; + else req.headers['Content-Type'] = 'application/json'; return req; }); if (this.config.useClient) { diff --git a/interapp-frontend/src/app/announcements/page.tsx b/interapp-frontend/src/app/announcements/page.tsx index a2a5f42e..09c098d6 100644 --- a/interapp-frontend/src/app/announcements/page.tsx +++ b/interapp-frontend/src/app/announcements/page.tsx @@ -5,61 +5,52 @@ import { useState, useEffect } from 'react'; const handleFetch = async () => { const apiClient = new APIClient().instance; - const res = await apiClient.get('/announcement/all', {params: {page: 1, page_size: 100}}); + const res = await apiClient.get('/announcement/all', { params: { page: 1, page_size: 100 } }); return res.data; -} - - +}; export default function AnnouncementsPage() { - const [A, setA] = useState(null); const [B, setB] = useState(null); - const handleSubmit = (event: React.FormEvent) => { - const apiClient = new APIClient({useMultiPart: true}).instance; + const apiClient = new APIClient({ useMultiPart: true }).instance; event.preventDefault(); const formData = new FormData(); - if (A) for (let i = 0; i < A.length; i++) { - formData.append('docs', A[i]); - } + if (A) + for (let i = 0; i < A.length; i++) { + formData.append('docs', A[i]); + } const body = { - 'creation_date': new Date().toISOString(), - 'title': 'test5', - 'description': 'test', - 'username': 'sebas' - } + creation_date: new Date().toISOString(), + title: 'test5', + description: 'test', + username: 'sebas', + }; for (const [key, value] of Object.entries(body)) { formData.append(key, value); } - apiClient.post('/announcement', formData).then((res) => { - console.log(res); - }).catch((err) => { - console.log(err); - }); - } + apiClient + .post('/announcement', formData) + .then((res) => { + console.log(res); + }) + .catch((err) => { + console.log(err); + }); + }; useEffect(() => { handleFetch().then((res) => setB(res)); }, []); - - - return ( <>
- setA(e.currentTarget.files)}/> + setA(e.currentTarget.files)} />
-
- { - JSON.stringify(B) - } - -
+
{JSON.stringify(B)}
- ); } diff --git a/interapp-frontend/src/app/services/AddService/AddService.tsx b/interapp-frontend/src/app/services/AddService/AddService.tsx index 8d4c8f1b..58731f1e 100644 --- a/interapp-frontend/src/app/services/AddService/AddService.tsx +++ b/interapp-frontend/src/app/services/AddService/AddService.tsx @@ -1,5 +1,5 @@ 'use client'; -import { Group, NumberInput, TextInput, Textarea, Button } from '@mantine/core'; +import { Group, NumberInput, TextInput, Textarea, Button, TagsInput } from '@mantine/core'; import { TimeInput } from '@mantine/dates'; import { IconPlus } from '@tabler/icons-react'; import { useForm } from '@mantine/form'; @@ -15,15 +15,16 @@ import UploadImage, { convertToBase64, allowedFormats } from '@components/Upload import './styles.css'; import { Permissions } from '@/app/route_permissions'; import { getAllUsernames } from '@api/utils'; -import PillsInputWithSearch from '@components/PillsInputWithSearch/PillsInputWithSearch'; import { useRouter } from 'next/navigation'; import { CreateServiceWithUsers } from '../types'; const AddService = ({ alreadyServiceICUsernames }: { alreadyServiceICUsernames: string[] }) => { const { user } = useContext(AuthContext); const [opened, setOpened] = useState(false); + const [allUsernames, setAllUsernames] = useState([]); const [allValidServiceICUsernames, setAllValidServiceICUsernames] = useState([]); + const [loading, setLoading] = useState(false); const apiClient = new APIClient().instance; const router = useRouter(); @@ -58,15 +59,19 @@ const AddService = ({ alreadyServiceICUsernames }: { alreadyServiceICUsernames: if (!value) return false; return value.toString().length !== 8 && 'Invalid phone number'; }, - website: (value) => { - if (!value) return false; - return !value.includes('http') && 'Invalid website'; - }, + end_time: (value, values) => value <= values.start_time && 'End time must be after start time', }, }); + useEffect(() => { + if (!form.values.service_ic_username) return; + if (!form.values.usernames.includes(form.values.service_ic_username)) { + form.setFieldValue('usernames', [...form.values.usernames, form.values.service_ic_username]); + } + }, [form.values.service_ic_username]); + const handleSubmit = async (data: CreateServiceWithUsers) => { setLoading(true); const serviceUsers = data.usernames; @@ -185,18 +190,21 @@ const AddService = ({ alreadyServiceICUsernames }: { alreadyServiceICUsernames: form.setFieldValue('service_ic_username', newServiceIc)} label='Service IC' required /> - form.setFieldValue('usernames', newServiceUsers)} + placeholder='Users that participate in this service regularly' required + data={allUsernames} + {...form.getInputProps('usernames')} /> + diff --git a/interapp-frontend/src/app/services/page.tsx b/interapp-frontend/src/app/services/page.tsx index 73e652a8..10218885 100644 --- a/interapp-frontend/src/app/services/page.tsx +++ b/interapp-frontend/src/app/services/page.tsx @@ -14,14 +14,7 @@ const fetchAllServices = async () => { try { const res = await apiClient.get('/service/all'); - switch (res.status) { - case 200: - break; - case 400: - throw new Error('Bad Request'); - default: - throw new Error('Unknown error'); - } + if (res.status !== 200) throw new Error(res.data); const allServices: Service[] = res.data; // promotional image url will look like this: diff --git a/interapp-frontend/src/app/services/styles.css b/interapp-frontend/src/app/services/styles.css index bd6b30c3..311f518e 100644 --- a/interapp-frontend/src/app/services/styles.css +++ b/interapp-frontend/src/app/services/styles.css @@ -4,7 +4,7 @@ .service-boxes { display: grid; - grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 1rem; } diff --git a/interapp-frontend/src/components/PillsInputWithSearch/PillsInputWithSearch.tsx b/interapp-frontend/src/components/PillsInputWithSearch/PillsInputWithSearch.tsx deleted file mode 100644 index 4d3b7aba..00000000 --- a/interapp-frontend/src/components/PillsInputWithSearch/PillsInputWithSearch.tsx +++ /dev/null @@ -1,108 +0,0 @@ -'use client'; -import { PillsInput, Pill, Combobox, CheckIcon, Group, useCombobox } from '@mantine/core'; -import { memo, useEffect, useState } from 'react'; - -export interface PillsInputWithSearchProps { - defaultValues?: T[]; - allValues: T[]; - onChange: (newValues: T[]) => void; - label?: string; - required?: boolean; - disabled?: boolean; - className?: string; -} - -const PillsInputWithSearch = ({ - defaultValues, - allValues, - onChange, - label, - required, - disabled, - className, -}: PillsInputWithSearchProps) => { - const combobox = useCombobox({ - onDropdownClose: () => combobox.resetSelectedOption(), - onDropdownOpen: () => combobox.updateSelectedOptionIndex('active'), - }); - const [search, setSearch] = useState(''); - - const [value, setValue] = useState([]); - - useEffect(() => { - if (defaultValues) setValue(defaultValues); - }, [defaultValues]); - - useEffect(() => { - onChange(value); - }, [value]); - - const handleValueSelect = (value: T) => { - // when an option is selected, add it to the list of values or remove it if it already exists - setValue((current) => - current.includes(value) ? current.filter((v) => v !== value) : [...current, value], - ); - }; - const handleValueRemove = (val: T) => setValue((current) => current.filter((v) => v !== val)); - - const options = allValues - .filter((v) => v.toLowerCase().includes(search.trim().toLowerCase())) - .map((v) => ( - - - {value.includes(v) ? : null} - {v} - - - )); - return ( - handleValueSelect(v as T)} - disabled={disabled ?? false} - > - - combobox.openDropdown()} - label={label} - required={required} - className={className} - > - - {value.map((item) => ( - handleValueRemove(item)} - > - {item} - - ))} - - - combobox.openDropdown()} - onBlur={() => combobox.closeDropdown()} - value={search} - placeholder='Search values' - onChange={(event) => { - combobox.updateSelectedOptionIndex(); - setSearch(event.currentTarget.value); - }} - disabled={disabled ?? false} - /> - - - - - - - - {options.length > 0 ? options : Nothing found...} - - - - ); -}; - -export default memo(PillsInputWithSearch);