diff --git a/.github/workflows/nepal.yml b/.github/workflows/nepal.yml index aa9711e..ec5b745 100644 --- a/.github/workflows/nepal.yml +++ b/.github/workflows/nepal.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - nepal jobs: build: @@ -12,6 +13,7 @@ jobs: env: VITE_API_URL: https://coachdigital.org/np/api/ VITE_COUNTRY: np + VITE_MAX_REGION_LEVEL: 2 steps: - name: ЁЯЫОя╕П Checkout uses: actions/checkout@v3 diff --git a/src/assets/images/logos/CoachLogoNP.png b/src/assets/images/logos/CoachLogoNP.png new file mode 100644 index 0000000..4428748 Binary files /dev/null and b/src/assets/images/logos/CoachLogoNP.png differ diff --git a/src/assets/images/logos/CoachLogoNP.svg b/src/assets/images/logos/CoachLogoNP.svg new file mode 100644 index 0000000..7599a9a --- /dev/null +++ b/src/assets/images/logos/CoachLogoNP.svg @@ -0,0 +1,2171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/logos/index.ts b/src/assets/images/logos/index.ts index 86f655a..2613ee7 100644 --- a/src/assets/images/logos/index.ts +++ b/src/assets/images/logos/index.ts @@ -1 +1,4 @@ -export { default as CoachLogo } from "./CoachLogo.png"; +import CoachLogoSL from './CoachLogo.png'; +import CoachLogoNP from './CoachLogoNP.svg'; + +export const CoachLogo = import.meta.env.VITE_COUNTRY === 'sl' ? CoachLogoSL : CoachLogoNP; diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 58b2dff..ba2ecd4 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -32,6 +32,7 @@ const Menu: React.FC> = ({ items, currentItem }) => { { zIndex={9999} > - setOpen(!open)} cursor="pointer" /> + setOpen(!open)} cursor="pointer" maxW="180px" /> {'Logo @@ -87,7 +87,7 @@ const Navbar: React.FC = () => { ) : ( <> - {'Logo + {'Logo {menu} )} diff --git a/src/i18n/langs/en.ts b/src/i18n/langs/en.ts index c56e11f..a44d1b0 100644 --- a/src/i18n/langs/en.ts +++ b/src/i18n/langs/en.ts @@ -11,6 +11,14 @@ const enTranslation = { cancel: 'Cancel', download: 'Download data', 'items-per-page': 'Items per page', + 'all-time': 'All time', + 'last-7-days': 'Last 7 days', + 'last-30-days': 'Last 30 days', + 'last-60-days': 'Last 60 days', + 'last-90-days': 'Last 90 days', + 'all-regions': 'All regions', + 'all-schools': 'All schools', + 'all-districts': 'All districts', }, Navbar: { @@ -100,6 +108,13 @@ const enTranslation = { 'teachers-count': 'Teachers count', actions: 'Actions', }, + form: { + new: 'New school', + update: 'Update school', + region: 'Region: ', + name: 'School name', + emis: 'Emis number', + }, }, 'coach-over-time': { @@ -194,10 +209,30 @@ const enTranslation = { region: { title: 'Regions', new: 'New region', + 'sub-subregion-title': 'Municipalities', + 'subregion-title': 'Districts', + 'new-subregion': 'New district', + 'new-sub-subregion': 'New municipality', edit: 'Edit', total_schools: 'Total school: {{ value }}', - form: { - name: 'Name', + placeholder: 'Region name', + 0: { + title: 'Regions', + new: 'New region', + placeholder: 'Region name', + select: 'Select a region', + }, + 1: { + title: 'Districts', + new: 'New district', + placeholder: 'District name', + select: 'Select a district', + }, + 2: { + title: 'Municipalities', + new: 'New Municipality', + placeholder: 'Municipality name', + select: 'Select a municipality', }, }, }, diff --git a/src/i18n/langs/np.ts b/src/i18n/langs/np.ts index f8b9bd4..fd17d16 100644 --- a/src/i18n/langs/np.ts +++ b/src/i18n/langs/np.ts @@ -9,6 +9,14 @@ const npTranslation = { generateQRCode: 'QR рдХреЛрдб рд╕рд┐рд░реНрдЬрдирд╛ рдЧрд░реНрдиреБрд╣реЛрд╕реН', cancel: 'рд░рджреНрдж рдЧрд░реНрдиреБрд╣реЛрд╕реН', download: 'рдбрд╛рдЯрд╛ рдбрд╛рдЙрдирд▓реЛрдб рдЧрд░реНрдиреБрд╣реЛрд╕реН', + 'all-time': 'рд╕рдзреИрдВ', + 'last-7-days': 'рдкрдЫрд┐рд▓реНрд▓реЛ рен рджрд┐рди', + 'last-30-days': 'рдкрдЫрд┐рд▓реНрд▓реЛ рейреж рджрд┐рди', + 'last-60-days': 'рдкрдЫрд┐рд▓реНрд▓реЛ ремреж рджрд┐рди', + 'last-90-days': 'рдкрдЫрд┐рд▓реНрд▓реЛ репреж рджрд┐рди', + 'all-regions': 'рд╕рдмреИ рдХреНрд╖реЗрддреНрд░рд╣рд░реВ', + 'all-schools': 'рд╕рдмреИ рд╡рд┐рджреНрдпрд╛рд▓рдпрд╣рд░реВ', + 'all-districts': 'рд╕рдмреИ рдЬрд┐рд▓реНрд▓рд╛рд╣рд░реВ', actions: 'рдХрд╛рд░реНрдпрд╣рд░реВ', }, Navbar: { @@ -181,6 +189,36 @@ const npTranslation = { language: { title: 'рднрд╛рд╖рд╛ рдкрд░рд┐рд╡рд░реНрддрди рдЧрд░реНрдиреБрд╣реЛрд╕реН', }, + + region: { + title: 'рдХреНрд╖реЗрддреНрд░рд╣рд░реВ', + new: 'рдирдпрд╛рдБ рдХреНрд╖реЗрддреНрд░', + 'sub-subregion-title': 'рдирдЧрд░рдкрд╛рд▓рд┐рдХрд╛рд╣рд░реВ', + 'subregion-title': 'рдЬрд┐рд▓реНрд▓рд╛рд╣рд░реВ', + 'new-subregion': 'рдирдпрд╛рдБ рдЬрд┐рд▓реНрд▓рд╛', + 'new-sub-subregion': 'рдирдпрд╛рдБ рдирдЧрд░рдкрд╛рд▓рд┐рдХрд╛', + edit: 'рд╕рдореНрдкрд╛рджрди рдЧрд░реНрдиреБрд╣реЛрд╕реН', + total_schools: 'рдХреБрд▓ рд╡рд┐рджреНрдпрд╛рд▓рдп: {{ value }}', + placeholder: 'рдХреНрд╖реЗрддреНрд░рдХреЛ рдирд╛рдо', + 0: { + title: 'рдХреНрд╖реЗрддреНрд░рд╣рд░реВ', + new: 'рдирдпрд╛рдБ рдХреНрд╖реЗрддреНрд░', + placeholder: 'рдХреНрд╖реЗрддреНрд░рдХреЛ рдирд╛рдо', + select: 'рдХреНрд╖реЗрддреНрд░ рдЫрд╛рдиреНрдиреБрд╣реЛрд╕реН', + }, + 1: { + title: 'рдЬрд┐рд▓реНрд▓рд╛рд╣рд░реВ', + new: 'рдирдпрд╛рдБ рдЬрд┐рд▓реНрд▓рд╛', + placeholder: 'рдЬрд┐рд▓реНрд▓рд╛рдХреЛ рдирд╛рдо', + select: 'рдЬрд┐рд▓реНрд▓рд╛ рдЫрд╛рдиреНрдиреБрд╣реЛрд╕реН', + }, + 2: { + title: 'рдирдЧрд░рдкрд╛рд▓рд┐рдХрд╛рд╣рд░реВ', + new: 'рдирдпрд╛рдБ рдирдЧрд░рдкрд╛рд▓рд┐рдХрд╛', + placeholder: 'рдирдЧрд░рдкрд╛рд▓рд┐рдХрд╛рдХреЛ рдирд╛рдо', + select: 'рдирдЧрд░рдкрд╛рд▓рд┐рдХрд╛ рдЫрд╛рдиреНрдиреБрд╣реЛрд╕реН', + }, + }, }, }, }; diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx index 9f435b0..1fe999f 100644 --- a/src/pages/Dashboard/index.tsx +++ b/src/pages/Dashboard/index.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; -import { Box, Button, Center, Flex, HStack, Select, Spinner, Text, VStack, useBreakpointValue } from '@chakra-ui/react'; -import { IDashboard, IRegion, ISchool, ITeachingPractices } from '@/types'; +import { Box, Button, Center, Flex, HStack, Text, VStack, useBreakpointValue } from '@chakra-ui/react'; +import { IDashboard, ITeachingPractices } from '@/types'; import DashboardService from '@/services/dashboard'; import Loader from '@/components/Base/Loader'; import { CardValue } from './components/CardValue'; @@ -9,21 +9,16 @@ import { DoughnutGraph } from './components/DoughnutGraph'; import { BarGraph } from './components/BarGraph'; import { HorizontalBar } from './components/HorizontalBar'; import { useUserContext } from '@/contexts/UserContext'; -import { ROLES } from '@/common/user'; -import SelectDistrict from '@/components/SelectDistrict'; import { useTranslation } from 'react-i18next'; -import RegionService from '@/services/region'; +import RegionSelect from '../Schools/SchoolForm/RegionSelect'; const DashboardPage: React.FC = () => { const { user } = useUserContext(); const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [dashboard, setDashboard] = useState(); - const [regions, setRegions] = useState(); const [regionId, setRegionId] = useState(user?.region_id); const [district, setDistrict] = useState(user?.district); - const [schoolName, setSchoolName] = useState(); - const [schoolList, setSchoolList] = useState([]); const [selected, setSelected] = useState(); const isMobile = useBreakpointValue({ base: true, md: false }); @@ -31,92 +26,24 @@ const DashboardPage: React.FC = () => { if (!loading) { setLoading(true); - if (!regions) { - RegionService.getRegions().then(setRegions); - } - - let schoolId: string | undefined; - - if (schoolName) { - schoolId = schoolList.find((school) => school.name === schoolName)?.id; - } - - DashboardService.getData(regionId, district, schoolId).then((data) => { + DashboardService.getData(regionId, district).then((data) => { const { schools, ...dash } = data; - if (schools) { - setSchoolList(schools); - } setDashboard(dash); setSelected(dash?.teachingPractices[0]); setLoading(false); }); } - }, [regionId, district, schoolName]); + }, [regionId, district]); - const handleRegion = (regionId: string) => { + const handleRegion = (regionId?: string) => { setRegionId(regionId); - setSchoolList([]); - setSchoolName(undefined); setDistrict(undefined); }; - const handleDistrict = (newDistrict: string) => { - setDistrict(newDistrict); - setSchoolList([]); - setSchoolName(undefined); - }; - return ( - - - {regions && ( - <> - {t('dashboard.filters.region')} - {user?.role === ROLES.admin ? ( - - ) : ( - {regions?.find((region) => region.id === regionId)?.name} - )} - - )} - - - - {t('dashboard.filters.district')} - {user?.role === ROLES.admin || user?.role === ROLES['region-analyst'] ? ( - handleDistrict(e.target.value)} - value={district} - /> - ) : ( - {district} - )} - - - - {t('dashboard.filters.school')} - {loading ? ( - - ) : ( - - )} - + + {loading || !dashboard || !selected ? ( diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index 3dad458..8cba9c2 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -1,13 +1,13 @@ -import { CoachLogo } from "@/assets/images/logos"; -import Loader from "@/components/Base/Loader"; -import { UserContext } from "@/contexts/UserContext"; -import { Button, Center, Image, Input, VStack } from "@chakra-ui/react"; -import React, { useContext } from "react"; -import { useForm, SubmitHandler, Controller } from "react-hook-form"; +import { CoachLogo } from '@/assets/images/logos'; +import Loader from '@/components/Base/Loader'; +import { UserContext } from '@/contexts/UserContext'; +import { Button, Center, Image, Input, VStack } from '@chakra-ui/react'; +import React, { useContext } from 'react'; +import { useForm, SubmitHandler, Controller } from 'react-hook-form'; const defaultValues = { - email: "", - password: "", + email: '', + password: '', }; const Login: React.FC = () => { @@ -26,32 +26,27 @@ const Login: React.FC = () => { }; return ( -
+
- + {isSubmitting ? ( -
+
) : ( <> - + ( - + )} /> @@ -60,21 +55,11 @@ const Login: React.FC = () => { rules={{ required: true }} name="password" render={({ field, fieldState: { error } }) => ( - + )} /> - diff --git a/src/pages/Schools/SchoolForm/RegionSelect/index.tsx b/src/pages/Schools/SchoolForm/RegionSelect/index.tsx new file mode 100644 index 0000000..52dadde --- /dev/null +++ b/src/pages/Schools/SchoolForm/RegionSelect/index.tsx @@ -0,0 +1,66 @@ +import RegionService from '@/services/region'; +import { IRegion } from '@/types'; +import { Flex, Select, Text, VStack } from '@chakra-ui/react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +type Props = { + direction: 'row' | 'column'; + parentId?: string; + level: number; + onSelect: (regionId?: string) => void; +}; + +const RegionSelect: React.FC = ({ level, direction, parentId, onSelect }) => { + const { t } = useTranslation(); + const [hasChildren, setHasChildren] = useState(false); + const [regions, setRegions] = useState([]); + const [selectedId, setSelectedId] = useState(); + + useEffect(() => { + setRegions([]); + setHasChildren(false); + setSelectedId(undefined); + onSelect(undefined); + RegionService.getRegionsByParentId(parentId).then(setRegions); + }, [parentId]); + + useEffect(() => { + if (selectedId) { + const region = regions.find((item) => item.id === selectedId); + const canSubmitValue = !region?.children || region.children.length === 0; + + if (canSubmitValue) { + onSelect(selectedId); + } else { + setHasChildren(true); + } + } + }, [selectedId]); + + return ( + + + {t(`settings.tabs.region.${level}.select`)} + + + {hasChildren && ( + + )} + + ); +}; + +export default RegionSelect; diff --git a/src/pages/Schools/SchoolForm/index.tsx b/src/pages/Schools/SchoolForm/index.tsx index 80ccbfc..d4f8405 100644 --- a/src/pages/Schools/SchoolForm/index.tsx +++ b/src/pages/Schools/SchoolForm/index.tsx @@ -11,62 +11,78 @@ import { DrawerFooter, FormControl, FormLabel, - Text, useToast, + Text, VStack, + Spinner, + HStack, + IconButton, } from '@chakra-ui/react'; import React, { ChangeEvent, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import RegionSelect from './RegionSelect'; +import SchoolService from '@/services/school'; +import Icon from '@/components/Base/Icon'; type Props = { isOpen: boolean; - school?: ISchool; - onSubmit: (competence: ISchool) => void; + schoolId?: string; + onSubmit: (school: ISchool) => void; onClose: () => void; - readonly?: boolean; }; -const SchoolForm: React.FC = ({ isOpen, school, onClose, onSubmit, readonly }) => { +const SchoolForm: React.FC = ({ isOpen, schoolId, onClose, onSubmit }) => { const { t } = useTranslation(); const toast = useToast(); - const [schoolValues, setSchoolValues] = useState({ - name: '', - emis_number: '', - }); + const [isLoading, setIsLoading] = useState(true); + const [showSelectRegion, setShowSelectRegion] = useState(false); + const [schoolValues, setSchoolValues] = useState(); useEffect(() => { - if (school) { - setSchoolValues(school); + setShowSelectRegion(false); + if (schoolId) { + setIsLoading(true); + SchoolService.getSchool(schoolId).then((value) => { + setSchoolValues(value); + setIsLoading(false); + if (!value.region_id) { + setShowSelectRegion(true); + } + }); } else { + setShowSelectRegion(true); setSchoolValues({ name: '', emis_number: '', + region_id: undefined, }); + setIsLoading(false); } - }, [isOpen, school]); + }, [isOpen, schoolId]); const handleInputChange = (e: ChangeEvent) => { - setSchoolValues({ - ...schoolValues, - [e.target.name]: e.target.value.toUpperCase(), - }); + if (schoolValues) + setSchoolValues({ + ...schoolValues, + [e.target.name]: e.target.value.toUpperCase(), + }); }; - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - if (!schoolValues.name) { - toast({ - title: 'School name is required.', - status: 'warning', - duration: 9000, - isClosable: true, - position: 'top-left', + const handleRegionChange = (region_id?: string) => { + if (schoolValues) { + setSchoolValues({ + ...schoolValues, + region_id, }); - return; } + }; - onSubmit(schoolValues); + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (schoolValues) { + setIsLoading(true); + onSubmit(schoolValues); + } }; return ( @@ -76,46 +92,57 @@ const SchoolForm: React.FC = ({ isOpen, school, onClose, onSubmit, readon
- {school ? (readonly ? 'View School' : 'Update school') : 'New School'} + {t(schoolId ? 'school.form.new' : 'school.form.update')} - - - - School name - - + {isLoading ? ( + + + + ) : ( + + + + {t('school.form.name')} + + - - Emis number - - - - + + {t('school.form.emis')} + + - {!readonly && ( - - - - + {showSelectRegion ? ( + + ) : ( + + + {t('school.form.region')} + {schoolValues?.region?.name} + + } + aria-label="Edit" + onClick={() => setShowSelectRegion(true)} + /> + + )} + + )} + + + + diff --git a/src/pages/Schools/index.tsx b/src/pages/Schools/index.tsx index b899cc7..4d4d8ee 100644 --- a/src/pages/Schools/index.tsx +++ b/src/pages/Schools/index.tsx @@ -25,7 +25,6 @@ const SchoolsPage: React.FC = () => { const [newSchool, setNewSchool] = useState(false); const [schools, setSchools] = useState([]); const [isLoadingList, setIsLoadingList] = useState(true); - const [isLoadingForm, setIsLoadingForm] = useState(false); const [isLoadingDelete, setIsLoadingDelete] = useState(false); const [schoolToEdit, setSchoolToEdit] = useState(); const [schoolToDelete, setSchoolToDelete] = useState(); @@ -47,13 +46,11 @@ const SchoolsPage: React.FC = () => { }; const saveSchool = async (school: Partial) => { - setIsLoadingForm(true); if (schoolToEdit) { await SchoolService.updateSchool({ ...schoolToEdit, ...school }); } else { await SchoolService.saveSchool(school); } - setIsLoadingForm(false); loadSchools(); closeForm(); }; @@ -82,7 +79,7 @@ const SchoolsPage: React.FC = () => { {isLoadingList ? ( diff --git a/src/pages/Settings/Regions/Form/index.tsx b/src/pages/Settings/Regions/Form/index.tsx index f0bac5c..3b1ba02 100644 --- a/src/pages/Settings/Regions/Form/index.tsx +++ b/src/pages/Settings/Regions/Form/index.tsx @@ -1,10 +1,14 @@ -import React, { useEffect } from 'react'; -import { Props } from './types'; -import { Controller, useForm } from 'react-hook-form'; +import React, { useEffect, useState } from 'react'; +import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { + Text, Input, + VStack, + HStack, Button, Drawer, + Spinner, + useTheme, FormLabel, DrawerBody, DrawerFooter, @@ -14,55 +18,82 @@ import { DrawerCloseButton, } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; +import RegionService from '@/services/region'; +import Icon from '@/components/Base/Icon'; +import { IRegion } from '@/types'; +import { Props } from './types'; +import RegionFormChildren from '../FormChildren'; -const RegionForm: React.FC = ({ defaultValues, handleSubmitForm, handleClose }) => { +const RegionForm: React.FC = ({ isOpen, regionId, handleSubmitForm, handleClose }) => { const { t } = useTranslation(); - const { - reset, - control, - handleSubmit, - formState: { isLoading }, - } = useForm({ defaultValues }); + const [loading, setLoading] = useState(true); + const [region, setRegion] = useState(); useEffect(() => { - reset(defaultValues); - }, [defaultValues]); + if (isOpen && regionId) { + setLoading(true); + RegionService.getRegion(regionId).then((data) => { + setRegion(data); + setLoading(false); + }); + } else { + setRegion({ name: '', children: [] }); + setLoading(false); + } + }, [isOpen, regionId]); + + const handleUpdateChildren = (children: IRegion[]) => { + if (region) { + setRegion({ ...region, children }); + } + }; + + const handleUpdateRegionName = (name: string) => { + setRegion({ ...region, name }); + }; + + const onSubmit = () => { + if (region) { + setLoading(true); + handleSubmitForm({ ...region, level: 0 }); + } + }; return ( - + -
- + - - {defaultValues && 'id' in defaultValues ? t('settings.tabs.region.edit') : t('settings.tabs.region.new')} - + {regionId ? t('settings.tabs.region.edit') : t('settings.tabs.region.new')} + {loading ? ( - {t('settings.tabs.region.form.name')} - ( - - )} + + + ) : ( + + handleUpdateRegionName(e.target.value)} + border="2px solid #ddd" /> + + + )} - - - - - + + + +
); diff --git a/src/pages/Settings/Regions/Form/types.ts b/src/pages/Settings/Regions/Form/types.ts index 6dc9c07..138801f 100644 --- a/src/pages/Settings/Regions/Form/types.ts +++ b/src/pages/Settings/Regions/Form/types.ts @@ -1,8 +1,8 @@ -import { IUser } from '@/types'; -import { SubmitHandler } from 'react-hook-form'; +import { IRegion } from '@/types'; export type Props = { - defaultValues: IUser; + isOpen: boolean; handleClose: () => void; - handleSubmitForm: SubmitHandler; + regionId?: IRegion['id']; + handleSubmitForm: (region: IRegion) => Promise; }; diff --git a/src/pages/Settings/Regions/FormChildren/index.tsx b/src/pages/Settings/Regions/FormChildren/index.tsx new file mode 100644 index 0000000..c5fed5e --- /dev/null +++ b/src/pages/Settings/Regions/FormChildren/index.tsx @@ -0,0 +1,85 @@ +import Icon from '@/components/Base/Icon'; +import { IRegion } from '@/types'; +import { Button, Divider, HStack, IconButton, Input, Text, VStack, useTheme } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; + +type Props = { + level: number; + children: IRegion[]; + + handleUpdate: (children: IRegion[]) => void; +}; + +const RegionFormChildren: React.FC = ({ level, children, handleUpdate }) => { + const { t } = useTranslation(); + const theme = useTheme(); + + const addChild = () => { + handleUpdate([...children, { name: '', level }]); + }; + + const handleUpdateChildren = (childrenToUpdate: IRegion[], indexToUpdate: number) => { + handleUpdate( + children.map((region, regionIndex) => + regionIndex === indexToUpdate ? { ...region, children: childrenToUpdate } : region, + ), + ); + }; + + const handleDeleteChild = (indexToDelete: number) => { + handleUpdate(children.filter((_, index) => index !== indexToDelete)); + }; + + const handleUpdateName = (name: string, indexToUpdate: number) => { + handleUpdate(children.map((item, index) => (index === indexToUpdate ? { ...item, name, level } : item))); + }; + + return ( + + + {t(`settings.tabs.region.${level}.title`)} + + + {children.map((region, index) => ( + + handleUpdateName(e.target.value, index)} + value={region?.name} + borderBottomRadius={level < parseInt(import.meta.env.VITE_MAX_REGION_LEVEL, 10) ? 0 : 8} + border="2px solid #ddd" + placeholder={t(`settings.tabs.region.${level}.placeholder`) || ''} + /> + + } + bg="#e53935" + transition="all 300ms" + onClick={() => handleDeleteChild(index)} + _hover={{ bg: '#ef9a9a' }} + /> + + {level < parseInt(import.meta.env.VITE_MAX_REGION_LEVEL, 10) && ( + handleUpdateChildren(items, index)} + /> + )} + + ))} + + addChild()}> + + {t(`settings.tabs.region.${level}.new`)} + + + ); +}; + +export default RegionFormChildren; diff --git a/src/pages/Settings/Regions/index.tsx b/src/pages/Settings/Regions/index.tsx index f86e258..4d2c29a 100644 --- a/src/pages/Settings/Regions/index.tsx +++ b/src/pages/Settings/Regions/index.tsx @@ -1,12 +1,10 @@ -import { useCallback, useContext, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import Icon from '@/components/Base/Icon'; import Loader from '@/components/Base/Loader'; import Menu from '@/components/Menu'; import { IRegion, IUser } from '@/types'; import { Center, HStack, Text, VStack, useTheme } from '@chakra-ui/react'; import RegionForm from './Form'; -import { SubmitHandler } from 'react-hook-form'; -import { UserContext } from '@/contexts/UserContext'; import { toast } from 'react-toastify'; import { useTranslation } from 'react-i18next'; import RegionService from '@/services/region'; @@ -14,7 +12,7 @@ import RegionService from '@/services/region'; const Regions = () => { const theme = useTheme(); const { t } = useTranslation(); - const { user } = useContext(UserContext); + const [formIsOpen, setFormIsOpen] = useState(false); const [currentRegion, setCurrentRegion] = useState(); const [regions, setRegions] = useState({ isLoading: true, @@ -29,10 +27,10 @@ const Regions = () => { refreshRegions(); }, [refreshRegions]); - const handleSubmitRegion: SubmitHandler = async (region) => { + const handleSubmitRegion = async (region: IRegion) => { try { setRegions({ isLoading: true, data: [] }); - if ('id' in region) { + if (!!region.id) { await RegionService.updateRegion(region.id, region); } else { await RegionService.saveRegion(region); @@ -41,26 +39,33 @@ const Regions = () => { toast.error('An error as ocurred on management of user'); } - setCurrentRegion(undefined); - refreshRegions(); + handleClose(); + await refreshRegions(); }; const menuOptions = [ { label: t('settings.tabs.region.edit'), - handleClick: (user: IUser) => setCurrentRegion(user), + handleClick: (user: IUser) => { + setFormIsOpen(true); + setCurrentRegion(user); + }, }, ]; + const handleClose = () => { + setFormIsOpen(false); + setCurrentRegion(undefined); + }; + return ( <> - {currentRegion && ( - setCurrentRegion(undefined)} - defaultValues={currentRegion as IUser} - handleSubmitForm={handleSubmitRegion} - /> - )} + @@ -73,33 +78,34 @@ const Regions = () => {
) : ( <> - {regions.data.map((currentUser) => ( + {regions.data.map((region) => (
+ - {currentUser.name} + {region.name} - {t('settings.tabs.region.total_schools', { value: currentUser.schoolsCount })} + {t('settings.tabs.region.total_schools', { value: region.schoolsCount })}
- {currentUser.id !== user?.id && } + ))} - setCurrentRegion({} as any)}> + setFormIsOpen(true)}> {t('settings.tabs.region.new')} diff --git a/src/services/region/index.ts b/src/services/region/index.ts index 797bd2f..45bc539 100644 --- a/src/services/region/index.ts +++ b/src/services/region/index.ts @@ -2,7 +2,10 @@ import _axios from '..'; import { IRegion } from '../../types'; export const RegionService = { + getRegionsByParentId: async (parentId?: string): Promise => + (await _axios.get(parentId ? `region/parent/${parentId}` : 'region/parent')).data, getRegions: async (): Promise => (await _axios.get('region')).data, + getRegion: async (id: string): Promise => (await _axios.get(`region/${id}`)).data, saveRegion: async (region: Partial): Promise => (await _axios.post('region', region)).data, updateRegion: async (regionId: string, region: IRegion): Promise => (await _axios.patch(`region/${regionId}`, region)).data, diff --git a/src/services/school/index.ts b/src/services/school/index.ts index 082997e..af52429 100644 --- a/src/services/school/index.ts +++ b/src/services/school/index.ts @@ -2,6 +2,7 @@ import _axios from '..'; import { ISchool } from '../../types'; export const SchoolService = { + getSchool: async (id: string): Promise => (await _axios.get(`school/${id}`)).data, getSchools: async (): Promise => (await _axios.get('school')).data, saveSchool: async (school: Partial): Promise => (await _axios.post('school', school)).data, updateSchool: async (school: ISchool): Promise => (await _axios.put('school', school)).data, diff --git a/src/types/index.ts b/src/types/index.ts index aad9085..eea0fee 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -23,8 +23,12 @@ export interface ICoach { } export interface IRegion { - id: string; + id?: string; name: string; + level?: number; + parent?: IRegion; + parent_id?: string; + children?: IRegion[]; schoolsCount?: number; }