From 655d90c8f3a5b28fc10c0884cb8a53a3d43d9eed Mon Sep 17 00:00:00 2001 From: LWangllix Date: Thu, 10 Aug 2023 02:52:39 +0300 Subject: [PATCH 1/3] init --- package.json | 4 +- src/App.tsx | 4 +- src/components/containers/EventsTable.tsx | 34 +++ src/components/fields/AsyncMultiSelect.tsx | 113 -------- src/components/fields/AsyncSelect.tsx | 137 ---------- .../fields/CreatableMultiSelect.tsx | 107 -------- src/components/fields/MonthDayField.tsx | 208 --------------- src/components/fields/MultiSelect.tsx | 109 -------- src/components/fields/PasswordField.tsx | 82 ------ src/components/fields/PhotoField.tsx | 173 ------------ src/components/fields/PhotoUploadField.tsx | 246 ------------------ src/components/fields/SuggestionsSelect.tsx | 122 --------- src/components/fields/TextAreaField.tsx | 108 -------- src/components/fields/TimePicker.tsx | 158 ----------- src/components/fields/utils/hooks.ts | 92 ------- src/components/other/AppLogo.tsx | 2 +- src/components/other/ButtonFilter.tsx | 62 +++++ src/components/other/Graphs.tsx | 87 ++++--- src/components/other/HeaderInfo.tsx | 67 +++++ src/components/other/HydroPopUp.tsx | 7 +- src/components/other/Icon.tsx | 4 +- src/components/other/MobileNavbar.tsx | 14 - src/components/other/Table.tsx | 9 +- src/index.tsx | 49 +++- src/pages/HydroPowerPlant.tsx | 210 ++++----------- src/pages/HydroPowerPlantsMap.tsx | 72 ++--- src/pages/HydroPowerPlantsTable.tsx | 11 +- src/setupProxy.js | 2 +- src/styles/globals.css | 15 -- src/utils/api.ts | 35 +-- src/utils/functions.tsx | 59 ++--- src/utils/hooks.ts | 155 ++++------- src/utils/texts.ts | 2 +- yarn.lock | 143 +++++++++- 34 files changed, 555 insertions(+), 2147 deletions(-) create mode 100644 src/components/containers/EventsTable.tsx delete mode 100644 src/components/fields/AsyncMultiSelect.tsx delete mode 100644 src/components/fields/AsyncSelect.tsx delete mode 100644 src/components/fields/CreatableMultiSelect.tsx delete mode 100644 src/components/fields/MonthDayField.tsx delete mode 100644 src/components/fields/MultiSelect.tsx delete mode 100644 src/components/fields/PasswordField.tsx delete mode 100644 src/components/fields/PhotoField.tsx delete mode 100644 src/components/fields/PhotoUploadField.tsx delete mode 100644 src/components/fields/SuggestionsSelect.tsx delete mode 100644 src/components/fields/TextAreaField.tsx delete mode 100644 src/components/fields/TimePicker.tsx create mode 100644 src/components/other/ButtonFilter.tsx create mode 100644 src/components/other/HeaderInfo.tsx delete mode 100644 src/styles/globals.css diff --git a/package.json b/package.json index e32cb79..7c5f624 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "dependencies": { "@material-ui/core": "^4.12.4", "@reduxjs/toolkit": "^1.9.3", + "@sentry/react": "^7.62.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -34,6 +35,7 @@ "react-icons": "^4.8.0", "react-leaflet": "^4.2.1", "react-paginate": "^8.2.0", + "react-query": "^3.39.3", "react-redux": "^8.0.5", "react-resize-detector": "^8.0.4", "react-router": "^6.10.0", @@ -87,10 +89,10 @@ ] }, "devDependencies": { + "@microsoft/eslint-formatter-sarif": "^3.0.0", "@types/lodash": "^4.14.192", "@types/react-datepicker": "^4.10.0", "@types/styled-components": "^5.1.26", - "@microsoft/eslint-formatter-sarif": "^3.0.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.0.0" }, diff --git a/src/App.tsx b/src/App.tsx index c72ad9d..0b8b525 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,7 +18,7 @@ Interaction.modes.interpolate = Interpolate; moment.locale("lt"); -function App() { +const App = () => { useEffect(() => { initGA(); logPageView(); @@ -39,6 +39,6 @@ function App() { ); -} +}; export default App; diff --git a/src/components/containers/EventsTable.tsx b/src/components/containers/EventsTable.tsx new file mode 100644 index 0000000..639d563 --- /dev/null +++ b/src/components/containers/EventsTable.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; +import { useSearchParams } from "react-router-dom"; +import { handlePagination, mapEvents } from "../../utils/functions"; +import { eventLabels } from "../../utils/texts"; +import Table, { TableData } from "../other/Table"; + +const EventTable = ({ events, hydroPowerPlant, loading }) => { + const [tableData, setTableData] = useState({ data: [] }); + const [searchParams] = useSearchParams(); + const { page } = Object.fromEntries([...Array.from(searchParams)]); + + useEffect(() => { + const pageData = handlePagination({ + data: events, + page: page, + pageSize: 40 + }); + setTableData({ + data: mapEvents({ ...hydroPowerPlant!, events: pageData.slicedData }), + totalPages: pageData.totalPages + }); + }, [page, loading, events, hydroPowerPlant]); + + return ( + + ); +}; + +export default EventTable; diff --git a/src/components/fields/AsyncMultiSelect.tsx b/src/components/fields/AsyncMultiSelect.tsx deleted file mode 100644 index b6a91af..0000000 --- a/src/components/fields/AsyncMultiSelect.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import FieldWrapper from "./components/FieldWrapper"; -import MultiTextField from "./components/MultiTextFieldInput"; -import OptionsContainer from "./components/OptionsContainer"; -import { filterSelectedOptions, handleRemove } from "./utils/functions"; -import { useAsyncSelectData } from "./utils/hooks"; - -export interface SelectOption { - id?: string; - label?: string; - [key: string]: any; -} - -export interface SelectFieldProps { - id?: string; - name?: string; - label?: string; - values?: any[]; - error?: string; - showError?: boolean; - editable?: boolean; - left?: JSX.Element; - right?: JSX.Element; - padding?: string; - onChange: (option: any) => void; - handleLogs?: (data: any) => void; - disabled?: boolean; - className?: string; - placeholder?: string; - backgroundColor?: string; - hasBorder?: boolean; - getOptionLabel?: (option: any) => string; - getOptionValue?: (option: any) => any; - isSearchable?: boolean; - setSuggestionsFromApi: (input: any, page: number, id?: any) => any; - dependantId?: string; - handleRefresh?: () => void; - optionsKey?: string; -} - -const AsyncMultiSelect = ({ - label, - values = [], - name, - error, - hasBorder, - showError = true, - editable = true, - className, - left, - right, - padding, - optionsKey = "rows", - onChange, - handleLogs, - disabled = false, - backgroundColor, - getOptionLabel = (option) => option.label, - getOptionValue = (option) => option.id, - isSearchable = false, - setSuggestionsFromApi, - dependantId, - handleRefresh, - ...rest -}: SelectFieldProps) => { - const { - loading, - handleScroll, - suggestions, - handleInputChange, - handleToggleSelect, - input, - showSelect, - handleBlur, - handleClick - } = useAsyncSelectData({ - setSuggestionsFromApi, - disabled, - onChange: (option: any) => onChange([...values, option]), - dependantId, - optionsKey - }); - - return ( - - { - handleRemove(index, onChange, values); - }} - input={input} - error={error} - disabled={disabled} - handleInputChange={handleInputChange} - getOptionLabel={getOptionLabel} - /> - - - ); -}; - -export default AsyncMultiSelect; diff --git a/src/components/fields/AsyncSelect.tsx b/src/components/fields/AsyncSelect.tsx deleted file mode 100644 index 41bf230..0000000 --- a/src/components/fields/AsyncSelect.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import styled from "styled-components"; -import { inputLabels } from "../../utils/texts"; -import Icon from "../other/Icon"; -import FieldWrapper from "./components/FieldWrapper"; -import OptionsContainer from "./components/OptionsContainer"; -import TextFieldInput from "./components/TextFieldInput"; -import { useAsyncSelectData } from "./utils/hooks"; - -export interface AsyncSelectFieldProps { - id?: string; - name?: string; - label?: string; - value?: any; - error?: string; - showError?: boolean; - editable?: boolean; - left?: JSX.Element; - handleLogs?: (data: any) => void; - right?: JSX.Element; - padding?: string; - onChange: (option: any) => void; - disabled?: boolean; - getOptionLabel: (option: any) => string; - getInputLabel?: (option: any) => string; - className?: string; - placeholder?: string; - backgroundColor?: string; - hasBorder?: boolean; - setSuggestionsFromApi: (input: any, page: number, id?: any) => any; - getOptionValue?: (option: any) => any; - dependantId?: string; - optionsKey?: string; - hasOptionKey?: boolean; - primaryKey?: string; - haveIncludeOptions?: boolean; -} - -const AsyncSelectField = ({ - label, - value, - error, - showError = true, - className, - padding, - hasOptionKey = true, - optionsKey = "rows", - onChange, - name, - disabled = false, - getOptionLabel = (option) => option.label, - getOptionValue = (option) => option.id, - setSuggestionsFromApi, - dependantId, - placeholder = inputLabels.chooseOption -}: AsyncSelectFieldProps) => { - const { - loading, - handleScroll, - suggestions, - handleInputChange, - handleToggleSelect, - input, - showSelect, - handleBlur, - handleClick - } = useAsyncSelectData({ - setSuggestionsFromApi, - disabled, - onChange, - dependantId, - optionsKey, - hasOptionKey - }); - return ( - - - {value && !disabled && ( - !disabled && handleClick(undefined)} - > - - - )}{" "} - - } - onChange={handleInputChange} - leftIcon={} - disabled={disabled} - placeholder={(value && getOptionLabel(value)) || placeholder} - selectedValue={value} - /> - - - - ); -}; - -const ClearIcon = styled(Icon)<{ disabled: boolean }>` - color: #cdd5df; - font-size: 2.2rem; - - cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")}; -`; - -const IconContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; -`; - -const StyledIcon = styled(Icon)` - color: #cdd5df; - font-size: 2.4rem; - margin-right: 12px; -`; - -export default AsyncSelectField; diff --git a/src/components/fields/CreatableMultiSelect.tsx b/src/components/fields/CreatableMultiSelect.tsx deleted file mode 100644 index b7365dd..0000000 --- a/src/components/fields/CreatableMultiSelect.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { useState } from "react"; -import FieldWrapper from "./components/FieldWrapper"; -import MultiTextField from "./components/MultiTextFieldInput"; -import OptionsContainer from "./components/OptionsContainer"; -import { handleRemove } from "./utils/functions"; - -export interface SelectOption { - id?: string; - label?: string; - [key: string]: any; -} - -export interface SelectFieldProps { - id?: string; - name: string; - label?: string; - values?: any; - error?: string; - showError?: boolean; - editable?: boolean; - padding?: string; - onChange: (option: any) => void; - handleLogs?: (data: any) => void; - disabled?: boolean; - className?: string; - placeholder?: string; - backgroundColor?: string; - isSearchable?: boolean; -} - -const CreatableMultiSelect = ({ - label, - values, - error, - showError = true, - className, - placeholder = "Įveskite sinonimus", - padding, - onChange, - disabled = false -}: SelectFieldProps) => { - const [input, setInputValue] = useState(""); - const [showSelect, setShowSelect] = useState(false); - const isExist = values.some((value: any) => value === input); - - const handleBlur = (event: any) => { - if (!event.currentTarget.contains(event.relatedTarget)) { - handleAdd(); - } - }; - - const clear = () => { - setShowSelect(false); - setInputValue(""); - }; - - const handleAdd = () => { - if (!isExist && input) { - onChange([...values, input]); - } - clear(); - }; - - const handleKeyDown = (event: any) => { - if (event.key === "Enter") { - handleAdd(); - } - }; - - return ( - - { - handleRemove(index, onChange, values); - }} - disabled={disabled} - handleInputChange={(input) => { - setShowSelect(input?.length > 0); - setInputValue(input); - }} - getOptionLabel={(option) => `${option}`} - /> - `${option}`} - showSelect={showSelect} - handleClick={handleAdd} - /> - - ); -}; - -export default CreatableMultiSelect; diff --git a/src/components/fields/MonthDayField.tsx b/src/components/fields/MonthDayField.tsx deleted file mode 100644 index 72dc5c0..0000000 --- a/src/components/fields/MonthDayField.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import styled from "styled-components"; -export interface NumericTextFieldProps { - value?: string | number; - name?: string; - error?: string; - showError?: boolean; - label?: string | JSX.Element; - icon?: JSX.Element; - className?: string; - left?: JSX.Element; - right?: JSX.Element; - padding?: string; - onChange: (option: any) => void; - onClick?: () => void; - ref?: HTMLHeadingElement; - bottomLabel?: string; - disabled?: boolean; - height?: number; - readOnly?: boolean; - onInputClick?: () => void; - secondLabel?: JSX.Element; - subLabel?: string; -} - -const MonthDayField = ({ - value, - name, - error, - showError = true, - label, - className, - left, - right, - padding, - onChange, - onClick, - disabled, - height, - onInputClick -}: NumericTextFieldProps) => { - const getMonthAndDay = (value: string | number) => { - //@ts-ignore - const [, month, day] = value?.replace(/-/g, "").match(/(\d{0,2})(\d{0,2})/); - - return { month, day }; - }; - - const handleBlur = (event: any) => { - if (!event.currentTarget.contains(event.relatedTarget)) { - const { month, day } = getMonthAndDay(value!); - if (day && day.length !== 2) { - onChange(`${month}-0${day[0]}`); - } - - if (!day) { - onChange(""); - } - } - }; - - const handleChange = (e: any) => { - let input: string = e.target.value; - const regex = /^[0-9]{0,2}-?[0-9]{0,2}$/; - - const { month, day } = getMonthAndDay(input); - - if (regex.test(input)) { - if (input[1] == "-") { - return onChange(`0${month[0]}-`); - } - - if (month > 12) { - return onChange(`0${month[0]}-${month[1]}`); - } - - if (day > 31) { - return onChange(`${month}-31`); - } - - if (day) { - return onChange(`${month}-${day}`); - } - - onChange(input); - } - }; - - return ( - - {!!label && ( - - - - )} - - {left} - (onInputClick ? onInputClick() : null)} - readOnly={!!disabled} - type="text" - name={name} - autoComplete="off" - value={value === 0 ? "0" : value || ""} - onChange={handleChange} - placeholder={"01-31"} - min={"0"} - disabled={disabled} - /> - {right} - - {showError && !!error && {error}} - - ); -}; - -const Container = styled.div<{ padding: string }>` - display: block; - padding: ${({ padding }) => padding}; -`; - -const InputContainer = styled.div<{ - error: boolean; - height: number; - disabled: boolean; -}>` - display: flex; - height: ${({ height }) => (height ? `${height}px` : `40px`)}; - background-color: white; - justify-content: space-between; - border-radius: 4px; - overflow: hidden; - border: 1px solid - ${({ theme, error }) => (error ? theme.colors.error : theme.colors.border)}; - opacity: ${({ disabled }) => (disabled ? 0.48 : 1)}; - :focus-within { - border-color: ${({ theme }) => theme.colors.primary}; - box-shadow: 0 0 0 4px ${({ theme }) => `${theme.colors.primary}33`}; - } -`; - -const TextInput = styled.input<{ readOnly: boolean }>` - border: none; - padding: 0 12px; - width: 100%; - display: inline-block; - cursor: ${({ readOnly }) => (readOnly ? "not-allowed" : "text")}; - - background-color: white; - font-size: 1.6rem; - color: ${({ theme }) => theme.colors.label}; - - &:focus { - outline: none; - } - - [type="number"] { - -moz-appearance: textfield; - } - ::-webkit-inner-spin-button, - ::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; - } - ::-webkit-input-placeholder { - color: ${({ theme }) => theme.colors.label + "8F"}; - } - ::-moz-placeholder { - color: ${({ theme }) => theme.colors.label + "8F"}; - } - ::-ms-placeholder { - color: ${({ theme }) => theme.colors.label + "8F"}; - } - ::placeholder { - color: ${({ theme }) => theme.colors.label + "8F"}; - } -`; - -const Label = styled.label` - text-align: left; - font-size: 1.4rem; - color: ${({ theme }) => theme.colors.label}; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const LabelContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - align-items: flex-start; -`; - -const ErrorMessage = styled.label` - width: 100%; - color: ${({ theme }) => theme.colors.error}; - font-size: 1.4rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -export default MonthDayField; diff --git a/src/components/fields/MultiSelect.tsx b/src/components/fields/MultiSelect.tsx deleted file mode 100644 index a525c5d..0000000 --- a/src/components/fields/MultiSelect.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import FieldWrapper from "./components/FieldWrapper"; -import MultiTextField from "./components/MultiTextFieldInput"; -import OptionsContainer from "./components/OptionsContainer"; -import { filterSelectedOptions, handleRemove } from "./utils/functions"; -import { useSelectData } from "./utils/hooks"; - -export interface SelectOption { - id?: string; - label?: string; - [key: string]: any; -} - -export interface SelectFieldProps { - id?: string; - name?: string; - label?: string; - values: any[]; - error?: string; - showError?: boolean; - editable?: boolean; - options: SelectOption[] | string[]; - left?: JSX.Element; - right?: JSX.Element; - padding?: string; - onChange: (option: any) => void; - handleLogs?: (data: any) => void; - disabled?: boolean; - className?: string; - placeholder?: string; - backgroundColor?: string; - hasBorder?: boolean; - getOptionLabel?: (option: any) => string; - getOptionValue?: (option: any) => any; - isSearchable?: boolean; - refreshOptions?: (id?: string) => any; - dependantId?: string; -} - -const MultiSelect = ({ - label, - values = [], - name, - error, - hasBorder, - showError = true, - editable = true, - options, - className, - left, - right, - padding, - onChange, - handleLogs, - disabled = false, - backgroundColor, - getOptionLabel = (option) => option.label, - getOptionValue = (option) => option.id, - refreshOptions, - isSearchable = false, - dependantId, - ...rest -}: SelectFieldProps) => { - const { - suggestions, - input, - handleToggleSelect, - showSelect, - handleBlur, - handleClick, - handleOnChange - } = useSelectData({ - options, - disabled, - getOptionLabel, - refreshOptions, - dependantId, - value: values, - onChange: (option: any) => onChange([...values, option]) - }); - return ( - - { - handleRemove(index, onChange, values); - }} - disabled={disabled} - handleInputChange={handleOnChange} - getOptionLabel={getOptionLabel} - /> - - - ); -}; - -export default MultiSelect; diff --git a/src/components/fields/PasswordField.tsx b/src/components/fields/PasswordField.tsx deleted file mode 100644 index 54fc879..0000000 --- a/src/components/fields/PasswordField.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useState } from "react"; -import styled from "styled-components"; -import Icon from "../other/Icon"; -import FieldWrapper from "./components/FieldWrapper"; -import TextFieldInput from "./components/TextFieldInput"; -export interface TextFieldProps { - value?: string | number; - name?: string; - error?: string; - showError?: boolean; - label?: string; - className?: string; - padding?: string; - onChange?: (option?: any) => void; - bottomLabel?: string; - disabled?: boolean; - height?: number; - onInputClick?: () => void; - secondLabel?: JSX.Element; - placeholder?: string; -} - -const PasswordField = ({ - value, - secondLabel, - name, - error, - showError = true, - label, - className, - padding, - onChange, - placeholder, - disabled, - height, - onInputClick -}: TextFieldProps) => { - const [show, setShow] = useState(false); - - return ( - - setShow(!show)}> - - - } - onChange={onChange} - disabled={disabled} - height={height} - onInputClick={onInputClick} - placeholder={placeholder} - /> - - ); -}; - -const IconContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - padding: 0 12px; - cursor: pointer; -`; - -const StyledIcon = styled(Icon)` - color: #9aa4b2; - font-size: 2rem; -`; - -export default PasswordField; diff --git a/src/components/fields/PhotoField.tsx b/src/components/fields/PhotoField.tsx deleted file mode 100644 index 54fd2f8..0000000 --- a/src/components/fields/PhotoField.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { useState } from "react"; -import styled from "styled-components"; -import { device } from "../../styles"; -import { FileProps } from "../../utils/types"; -import Icon from "../other/Icon"; -import LoaderComponent from "../other/LoaderComponent"; - -export interface PhotoFieldProps { - photo: FileProps | File | any; - handleDelete?: (id: string, index: number) => void; - onImageClick?: () => void; - disabled?: boolean; - index: number; - isOpen?: boolean; - height?: number; - getSrc: (photo: any) => string; -} - -const PhotoField = ({ - handleDelete, - disabled = false, - index, - photo, - height = 100, - isOpen, - getSrc, - onImageClick -}: PhotoFieldProps) => { - const [loading, setLoading] = useState(true); - - const isMain = photo.main; - - const handleDeleteClickClick = (e: any) => { - e.stopPropagation(); - if (!handleDelete) return; - - handleDelete(photo.id, index); - }; - - return ( - - {!isOpen && !disabled && !loading && ( - - - - )} - setLoading(false)} - /> - {isMain && ( - <> - - Pagrindinė nuotrauka - - )} - {loading && ( - - - - )} - - ); -}; - -const ImageLayer = styled.div` - transition: 0.5s ease; - opacity: 1; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - -ms-transform: translate(-50%, -50%); - text-align: center; - display: flex; - justify-content: center; - align-items: center; -`; - -const StyledCloseIcon = styled(Icon)` - font-size: 2.4rem; - color: ${({ theme }) => theme.colors.danger}; -`; -const StyledCloseIconContainer = styled.div` - position: absolute; - top: 0px; - right: 0px; - opacity: 0; - display: none; - cursor: pointer; - z-index: 10; -`; - -const MainPhotoBackground = styled.div` - position: absolute; - bottom: 0; - height: 19px; - background-color: #000000; - border-radius: 0px 0px 2px 2px; - opacity: 0.49; - width: 100%; - display: flex; - align-items: center; - justify-content: center; -`; - -const MainPhotoText = styled.div` - font-size: 1rem; - color: #ffffff; - position: absolute; - bottom: 2px; - left: 7px; -`; - -export const StyledImg = styled.img<{ - height: number; - disabled: boolean; - display: boolean; - isOpen: boolean; -}>` - width: fit-content; - height: ${({ height }) => `${height}px`}; - object-fit: cover; - border-radius: 4px; - cursor: ${({ isOpen }) => (isOpen ? "block" : "pointer")}; - opacity: 1; - display: ${({ display }) => (display ? "block" : "none")}; - max-width: 100%; - transition: 0.5s ease; - backface-visibility: hidden; - max-width: 100%; - - ${({ isOpen }) => - isOpen && - `@media ${device.mobileL} { - height: 100%; - width: 100%; - } - `} -`; - -const ImageContainer = styled.div<{ - isOpen: boolean; - main: boolean; -}>` - position: relative; - height: 100%; - min-width: 100px; - min-height: 100px; - border-radius: 4px; - border: ${({ main }) => (main ? "2px solid #FEBC1D" : "none")}; - - ${({ isOpen }) => - !isOpen && - ` - &:hover ${StyledCloseIconContainer} { - opacity: 1; - display:block; - } - `} -`; - -export default PhotoField; diff --git a/src/components/fields/PhotoUploadField.tsx b/src/components/fields/PhotoUploadField.tsx deleted file mode 100644 index 730e7ce..0000000 --- a/src/components/fields/PhotoUploadField.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import { useState } from "react"; -import styled from "styled-components"; -import { device } from "../../styles"; -import { handleAlert, validateFileTypes } from "../../utils/functions"; -import { inputLabels } from "../../utils/texts"; -import { FileProps } from "../../utils/types"; -import Icon from "../other/Icon"; -import Loader from "../other/Loader"; -import Modal from "../other/Modal"; -import PhotoField from "./PhotoField"; - -export interface PhotoUploadFieldProps { - name: string; - photos: FileProps[] | File[] | any[]; - handleDelete?: (id: string, index: number) => void; - onUpload?: (files: File[]) => void; - onImageClick?: (files: File[]) => void; - disabled?: boolean; - canOpenPhoto?: boolean; - getSrc: (photo: any) => string; - error?: string; - showError?: boolean; -} -export const availablePhotoMimeTypes = ["image/png", "image/jpg", "image/jpeg"]; - -const PhotoUploadField = ({ - photos, - name, - handleDelete, - disabled = false, - onUpload, - getSrc, - error, - showError = true -}: PhotoUploadFieldProps) => { - const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(-1); - const [loading, setLoading] = useState(false); - - const selectedPhoto = photos?.[selectedPhotoIndex]; - - const handleSetFiles = async (currentFiles: File[]) => { - const isValidFileTypes = validateFileTypes( - currentFiles, - availablePhotoMimeTypes - ); - if (!isValidFileTypes) return handleAlert("badFileTypes"); - if (onUpload) { - setLoading(true); - await onUpload(currentFiles); - - setLoading(false); - } - }; - - const handleOpenPreviousPhoto = () => { - const currentIndex = - (selectedPhotoIndex - 1 + photos.length) % photos.length; - - handleOpenPhoto(currentIndex); - }; - - const handleOpenNextPhoto = () => { - const currentIndex = (selectedPhotoIndex + 1) % photos.length; - handleOpenPhoto(currentIndex); - }; - - const handleOpenPhoto = (currentIndex: number) => { - setSelectedPhotoIndex(currentIndex); - }; - - return ( - - {photos.map((photo: File | FileProps | any, index: number) => { - if (!photo) return; - - return ( -
- handleOpenPhoto(index)} - /> -
- ); - })} - - {loading && ( - - - - )} - - setSelectedPhotoIndex(-1)} - > - - {photos.length > 1 && ( -
- -
- )} - - {photos.length > 1 && ( -
- -
- )} -
-
- - {!disabled && ( - - - - { - handleSetFiles(Array.from(e?.target?.files)); - }} - /> - {inputLabels.uploadPhotos} - - )} - {showError && !!error && {error}} -
- ); -}; - -const LoaderContainer = styled.div` - width: 133px; - height: 100px; - display: flex; - justify-content: center; - align-items: center; -`; - -export const StyledImg = styled.img<{ disabled: boolean }>` - width: fit-content; - height: 100px; - border-radius: 5px; - cursor: pointer; - opacity: ${({ disabled }) => (disabled ? 0.48 : 1)}; - transition: 0.5s ease; - backface-visibility: hidden; - max-width: 100%; -`; - -const StyledInput = styled.input``; -const StyledText = styled.div` - color: #7a7e9f; - font-size: 1rem; - line-height: 10px; - margin-top: 8px; -`; - -const StyledButton = styled.button<{ - error: boolean; -}>` - border-width: ${({ error }) => (error ? "1px" : "2px ")}; - border-color: ${({ error }) => (error ? "red" : "#d3d2d2 ")}; - border-style: ${({ error }) => (error ? "solid" : "dashed")}; - width: 133px; - height: 100px; - padding: 1rem; - border-radius: 5px; - background-color: #eeebe53d; - position: relative; - input { - position: absolute; - top: 0; - right: 0; - margin: 0; - padding: 0; - cursor: pointer; - opacity: 0; - width: 133px; - height: 100px; - } -`; - -const StyledIcon = styled(Icon)` - cursor: pointer; - font-size: 2.4rem; - color: #697586; -`; - -const StyledArrow = styled(Icon)` - cursor: pointer; - font-size: 3.2rem; - color: white; -`; - -const StyledCloseIconContainer = styled.div` - position: absolute; - top: 10px; - right: 20px; - cursor: pointer; - z-index: 10; -`; - -const Container = styled.div` - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 12px; -`; - -const InnerContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - gap: 16px; - @media ${device.mobileL} { - width: 100%; - } -`; - -const ErrorMessage = styled.label` - display: inline-block; - width: 100%; - color: ${({ theme }) => theme.colors.error}; - font-size: 1.4rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - height: 2.4rem; -`; - -export default PhotoUploadField; diff --git a/src/components/fields/SuggestionsSelect.tsx b/src/components/fields/SuggestionsSelect.tsx deleted file mode 100644 index f8a35e9..0000000 --- a/src/components/fields/SuggestionsSelect.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useState } from "react"; -import FieldWrapper from "./components/FieldWrapper"; -import OptionsContainer from "./components/OptionsContainer"; -import TextFieldInput from "./components/TextFieldInput"; -import { getFilteredOptions } from "./utils/functions"; - -export interface SelectFieldProps { - id?: string; - name?: string; - label?: string; - value?: any; - error?: string; - showError?: boolean; - readOnly?: boolean; - options?: any[]; - left?: JSX.Element; - right?: JSX.Element; - padding?: string; - onChange: (option: any) => void; - onSelect: (option: any) => void; - disabled?: boolean; - getOptionLabel: (option: any) => string; - getInputLabel?: (option: any) => string; - className?: string; - placeholder?: string; - backgroundColor?: string; - hasBorder?: boolean; - isClearable?: boolean; - dependantId?: string; - refreshOptions?: (dependantId?: string) => any; -} - -const SuggestionsSelect = ({ - label, - value, - name, - error, - hasBorder, - showError = true, - readOnly = false, - placeholder, - options, - className, - left, - right, - padding, - getOptionLabel, - onChange, - disabled, - backgroundColor, - getInputLabel, - isClearable = false, - dependantId, - onSelect, - refreshOptions, - ...rest -}: SelectFieldProps) => { - const [input, setInputValue] = useState(null); - const [showSelect, setShowSelect] = useState(false); - - const [suggestions, setSuggestions] = useState(options); - - const handleToggleSelect = () => { - !disabled && setShowSelect(!showSelect); - }; - - const handleBlur = (event: any) => { - if (!event.currentTarget.contains(event.relatedTarget)) { - setShowSelect(false); - } - }; - - const handleOnChange = (input: string) => { - if (!options) return; - - if (input) { - setShowSelect(true); - } - setInputValue(input); - onChange(input); - setSuggestions(getFilteredOptions(options, input, getOptionLabel)); - }; - - return ( - - - { - setInputValue(getOptionLabel(value)); - onSelect(val); - setShowSelect(false); - }} - /> - - ); -}; - -export default SuggestionsSelect; diff --git a/src/components/fields/TextAreaField.tsx b/src/components/fields/TextAreaField.tsx deleted file mode 100644 index 78096df..0000000 --- a/src/components/fields/TextAreaField.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { useEffect } from "react"; -import { useResizeDetector } from "react-resize-detector"; -import styled from "styled-components"; -import FieldWrapper from "./components/FieldWrapper"; - -export interface TextFieldProps { - value?: string | number; - name?: string; - error?: string; - showError?: boolean; - label?: string; - icon?: JSX.Element; - className?: string; - left?: JSX.Element; - right?: JSX.Element; - padding?: string; - onChange: (props: any) => void; - onClick?: () => void; - rows?: number; - placeholder?: string; - disabled?: boolean; -} - -const TextAreaField = (props: TextFieldProps) => { - const { - value, - name, - error, - showError = true, - label, - className, - onChange, - onClick, - rows = 5, - placeholder, - padding, - disabled = false - } = props; - - const { width, ref } = useResizeDetector(); - - useEffect(() => { - if (rows * 20 < ref.current.scrollHeight) { - ref.current.style.height = "auto"; - ref.current.style.height = ref.current.scrollHeight + "px"; - } - }, [ref, value, width]); - - return ( - - - onChange && onChange(e.target.value || "")} - /> - - - ); -}; - -const InputContainer = styled.div<{ error: boolean; disabled: boolean }>` - border: 1px solid - ${({ theme, error }) => (error ? theme.colors.error : theme.colors.border)}; - display: flex; - height: auto; - overflow: hidden; - justify-content: space-between; - padding: 8px; - box-sizing: border-box; - background-color: white; - opacity: ${({ disabled }) => (disabled ? 0.48 : 1)}; - border: 1px solid - ${({ theme, error }) => (error ? theme.colors.error : theme.colors.border)}; - border-radius: 4px; - :focus-within { - border-color: ${({ theme }) => theme.colors.primary}; - box-shadow: 0 0 0 4px ${({ theme }) => `${theme.colors.primary}33`}; - } -`; - -const StyledTextArea = styled.textarea` - border: none; - font-size: 1.6rem; - line-height: 2rem; - width: 100%; - overflow-y: hidden; - resize: none; - color: ${({ theme }) => theme.colors.label}; - cursor: ${({ disabled }) => (disabled ? "not-allowed" : "text")}; - background-color: transparent; - :focus { - outline: none; - } -`; - -export default TextAreaField; diff --git a/src/components/fields/TimePicker.tsx b/src/components/fields/TimePicker.tsx deleted file mode 100644 index de120d1..0000000 --- a/src/components/fields/TimePicker.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { format } from "date-fns"; -import lt from "date-fns/locale/lt"; -import { useState } from "react"; -import DatePicker, { registerLocale } from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; -import styled from "styled-components"; -import TextField from "../fields/TextField"; -import Icon from "../other/Icon"; - -registerLocale("lt", lt); - -export interface TimepickerProps { - label?: string; - value?: Date; - error?: string; - padding?: string; - onChange: (option: any) => void; - disabled?: boolean; - className?: string; - maxDate?: Date; - minDate?: Date; -} - -const TimePicker = ({ - value, - error, - onChange, - label, - disabled, - padding, - className, - minDate, - maxDate -}: TimepickerProps) => { - const [open, setOpen] = useState(false); - - const handleBlur = (event: any) => { - if (!event.currentTarget.contains(event.relatedTarget)) { - setOpen(false); - } - }; - - const filterTime = (time: Date) => { - if (minDate && time < new Date(minDate)) { - return false; - } - - if (maxDate && time > new Date(maxDate)) { - return false; - } - - return true; - }; - - return ( - setOpen(!open)} - className={className} - tabIndex={1} - onBlur={handleBlur} - > - } - disabled={disabled} - /> - {open && !disabled ? ( - { - if (maxDate && date > new Date(maxDate)) { - return onChange(maxDate); - } - - if (minDate && date < new Date(minDate)) { - return onChange(minDate); - } - - onChange(date); - setOpen(false); - }} - inline - > - ) : null} - - ); -}; - -const TimeContainer = styled.div` - cursor: pointer; - &:focus { - outline: none; - } - width: 100%; - - position: relative; - .react-datepicker { - top: 80px; - position: absolute; - z-index: 8; - background-color: #ffffff; - box-shadow: 0px 2px 16px #121a5529; - border-radius: 10px; - border: none; - } - .react-datepicker--time-only { - width: 100%; - } - .react-datepicker__time-container { - width: 100% !important; - padding: 18px 0px; - } - .react-datepicker__time-box { - width: 100% !important; - text-align: start !important; - } - .react-datepicker__time-list-item { - font: normal normal 500 1.6rem/22px Manrope !important; - color: #121a55 !important; - } - .react-datepicker__time-list-item--disabled { - color: ${({ theme }) => theme.colors.grey} !important; - } - .react-datepicker__time-list-item--selected { - background-color: ${({ theme }) => theme.colors.secondary} !important; - } - .react-datepicker__header--time { - display: none; - } -`; - -const TimeIcon = styled(Icon)` - color: rgb(122, 126, 159); - vertical-align: middle; - margin-right: 8px; - font-size: 2.8rem; - align-self: center; -`; - -const StyledTextInput = styled(TextField)` - cursor: pointer !important; -`; - -export default TimePicker; diff --git a/src/components/fields/utils/hooks.ts b/src/components/fields/utils/hooks.ts index 5adb4e6..b95dbf7 100644 --- a/src/components/fields/utils/hooks.ts +++ b/src/components/fields/utils/hooks.ts @@ -1,98 +1,6 @@ -import { isEmpty } from "lodash"; import { useEffect, useState } from "react"; -import { GetAllResponse } from "../../../utils/api"; -import { handleResponse } from "../../../utils/functions"; import { getFilteredOptions } from "./functions"; -export const useAsyncSelectData = ({ - setSuggestionsFromApi, - disabled, - onChange -}: any) => { - const [loading, setLoading] = useState(false); - const [currentPage, setCurrentPage] = useState(0); - const [suggestions, setSuggestions] = useState([]); - const [hasMore, setHasMore] = useState(false); - const [input, setInput] = useState(""); - const [showSelect, setShowSelect] = useState(false); - - const handleBlur = (event: any) => { - if (!event.currentTarget.contains(event.relatedTarget)) { - setShowSelect(false); - setInput(""); - } - }; - - const handleClick = (option: any) => { - setShowSelect(false); - setInput(""); - setSuggestions([]); - setCurrentPage(0); - onChange(option); - }; - - useEffect(() => { - if (isEmpty(suggestions) && showSelect) { - handleLoadData("", 0); - } - }, [showSelect]); - - const handleLoadData = async ( - input: string, - page: number, - lazyLoading = false - ) => { - setLoading(true); - handleResponse({ - endpoint: () => setSuggestionsFromApi(input, page), - onSuccess: (list: GetAllResponse) => { - setCurrentPage(list.page); - setSuggestions( - lazyLoading ? [...suggestions, ...list.rows] : list.rows - ); - setHasMore(list.page < list.totalPages); - setLoading(false); - } - }); - }; - - const handleScroll = async (e: any) => { - const element = e.currentTarget; - const isTheBottom = - Math.abs( - element.scrollHeight - element.clientHeight - element.scrollTop - ) < 1; - - if (isTheBottom && hasMore && !loading) { - handleLoadData(input, currentPage + 1, true); - } - }; - - const handleToggleSelect = () => { - !disabled && setShowSelect(!showSelect); - }; - - const handleInputChange = (input: any) => { - if (input) { - setShowSelect(true); - } - setInput(input); - handleLoadData(input, 0); - }; - - return { - loading, - suggestions, - handleScroll, - input, - handleInputChange, - handleToggleSelect, - showSelect, - handleBlur, - handleClick - }; -}; - export const useSelectData = ({ options, disabled, diff --git a/src/components/other/AppLogo.tsx b/src/components/other/AppLogo.tsx index 6f6679f..9101fe2 100644 --- a/src/components/other/AppLogo.tsx +++ b/src/components/other/AppLogo.tsx @@ -10,7 +10,7 @@ const AppLogo = ({ textColor = "#040f2c" }: AppLogoProps) => { const navigate = useNavigate(); return ( navigate("/")}> - + {formLabels.hydroPowerPlant} ); diff --git a/src/components/other/ButtonFilter.tsx b/src/components/other/ButtonFilter.tsx new file mode 100644 index 0000000..60a0f55 --- /dev/null +++ b/src/components/other/ButtonFilter.tsx @@ -0,0 +1,62 @@ +import { isEqual } from "lodash"; +import { useState } from "react"; +import styled from "styled-components"; +import { TimeRanges } from "../../utils/constants"; +import { + getCustomTimeRangeToQuery, + timeRangeOptions, + timeRangeToQuery +} from "../../utils/functions"; +import { timeRangeLabels } from "../../utils/texts"; +import ButtonsGroup from "../buttons/ButtonsGroup"; +import Datepicker from "../fields/DatePicker"; +export interface LabelProps { + label: string; +} + +const ButtonFilter = ({ + timeFilter, + dateFilter, + onSetTimeFilter, + onSetDateFilter +}) => { + const [openCalendar, setOpenCalendar] = useState(false); + + const handleSetDateRange = (option: TimeRanges) => { + onSetTimeFilter(option); + + if (isEqual(option, TimeRanges.OTHER_DAY)) { + return setOpenCalendar(true); + } + onSetDateFilter(timeRangeToQuery[option]); + }; + + return ( + + timeRangeLabels[option]} + onChange={(option) => { + handleSetDateRange(option); + }} + isSelected={(option) => isEqual(timeFilter, option)} + /> + { + setOpenCalendar(false); + }} + value={dateFilter.time.$gte} + open={openCalendar} + onChange={(date) => onSetDateFilter(getCustomTimeRangeToQuery(date!))} + /> + + ); +}; + +export default ButtonFilter; + +const ButtonContainer = styled.div` + display: flex; + flex-wrap: wrap; + position: relative; +`; diff --git a/src/components/other/Graphs.tsx b/src/components/other/Graphs.tsx index 4557020..0dbbf4e 100644 --- a/src/components/other/Graphs.tsx +++ b/src/components/other/Graphs.tsx @@ -13,6 +13,8 @@ interface GraphsProps { current: HydroPowerPlant; } +const getLineColor = (violated: boolean) => (violated ? "#FE5B78" : "#0862AB"); + export const Graphs = ({ current, timeFilter }: GraphsProps) => { const unit = timeFilter.replace("other", "hour"); @@ -64,7 +66,6 @@ export const Graphs = ({ current, timeFilter }: GraphsProps) => { x: { ticks: { maxTicksLimit: 9 }, offset: true, - type: "time", time: { displayFormats: { @@ -78,6 +79,41 @@ export const Graphs = ({ current, timeFilter }: GraphsProps) => { } }; + const upperBasinOptions = { + ...commonOptions.plugins, + annotation: { + annotations: { + box: { + drawTime: "beforeDraw", + display: true, + type: "box", + yMin: current?.upperBasinMin || 0, + yMax: current?.upperBasinMax || 0, + borderColor: "#11E011", + borderWidth: 0, + backgroundColor: "#d3f8d392" + } + } + } + }; + + const lowerBasinOptions = { + ...commonOptions.plugins, + annotation: { + annotations: { + box: { + drawTime: "beforeDraw", + display: true, + type: "box", + yMin: current?.lowerBasinMin || 0, + borderColor: "#11E011", + borderWidth: 0, + backgroundColor: "#d3f8d392" + } + } + } + }; + const commonDataSetOptions = { borderWidth: 1, pointRadius: 0 @@ -86,8 +122,8 @@ export const Graphs = ({ current, timeFilter }: GraphsProps) => { const upperBasinDataChartData = { datasets: [ { - borderColor: violatedUpperBasinData ? "#FE5B78" : "#0862AB", - backgroundColor: violatedUpperBasinData ? "#FE5B78" : "#0862AB", + borderColor: getLineColor(violatedUpperBasinData), + backgroundColor: getLineColor(violatedUpperBasinData), data: upperBasinData, ...commonDataSetOptions } @@ -97,8 +133,8 @@ export const Graphs = ({ current, timeFilter }: GraphsProps) => { const lowerBasinChartData = { datasets: [ { - borderColor: violatedLowerBasinData ? "#FE5B78" : "#0862AB", - backgroundColor: violatedLowerBasinData ? "#FE5B78" : "#0862AB", + borderColor: getLineColor(violatedLowerBasinData), + backgroundColor: getLineColor(violatedLowerBasinData), data: lowerBasinData, ...commonDataSetOptions } @@ -110,24 +146,7 @@ export const Graphs = ({ current, timeFilter }: GraphsProps) => { { - {descriptions.basinMax} + {descriptions.upperBasinMax} { - {descriptions.basinMax} + {descriptions.upperBasinMax} diff --git a/src/components/other/HeaderInfo.tsx b/src/components/other/HeaderInfo.tsx new file mode 100644 index 0000000..ee8cc02 --- /dev/null +++ b/src/components/other/HeaderInfo.tsx @@ -0,0 +1,67 @@ +import styled from "styled-components"; +import { device } from "../../styles"; +import { descriptions } from "../../utils/texts"; +import { HydroPowerPlant } from "../../utils/types"; +export interface LabelProps { + label: string; +} + +const HeaderInfo = ({ + hydroPowerPlant +}: { + hydroPowerPlant: HydroPowerPlant; +}) => { + const { upperBasinMin, upperBasinMax, lowerBasinMin } = hydroPowerPlant; + + const info = [ + { value: upperBasinMin, description: descriptions.upperBasinMin }, + { value: upperBasinMax, description: descriptions.upperBasinMax }, + { value: lowerBasinMin, description: descriptions.lowerBasinMin } + ]; + + return ( + + {info.map((item) => ( + + {item?.value || "-"} + {item.description} + + ))} + + ); +}; + +export default HeaderInfo; + +const HeaderContainer = styled.div` + background-color: #0862ab; + padding: 24px 40px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + + @media ${device.mobileL} { + grid-template-columns: 1fr; + gap: 12px; + padding: 24px 20px; + } +`; +const HeaderInfoValue = styled.div` + font-size: 3.2rem; + font-weight: bold; + color: #ffffff; +`; + +const HeaderInfoItem = styled.div` + display: grid; + grid-template-columns: 1fr; + gap: 8px; + @media ${device.mobileL} { + grid-template-columns: 100px 1fr; + align-items: center; + } +`; + +const HeaderInfoText = styled.div` + font-size: 1.4rem; + color: #ffffff; +`; diff --git a/src/components/other/HydroPopUp.tsx b/src/components/other/HydroPopUp.tsx index 2854279..61db055 100644 --- a/src/components/other/HydroPopUp.tsx +++ b/src/components/other/HydroPopUp.tsx @@ -1,12 +1,11 @@ import { isEmpty } from "lodash"; -import moment from "moment"; import React from "react"; import { useNavigate } from "react-router"; import styled from "styled-components"; import { device } from "../../styles"; import { DateFormats, TimeRanges } from "../../utils/constants"; -import { inRange, lt } from "../../utils/functions"; +import { getTimeRangeLabel, inRange, lt } from "../../utils/functions"; import { slugs } from "../../utils/routes"; import { buttonsTitles, formLabels } from "../../utils/texts"; import { HydroPowerPlant, Range } from "../../utils/types"; @@ -38,9 +37,7 @@ const HydroPopUp = ({ const dateFrom = customDate.time.$gte; const dateTo = customDate.time.$lt; - const timeRangeLabel = `${moment(dateFrom).format(format)} - ${moment( - dateTo - ).format(format)}`; + const timeRangeLabel = getTimeRangeLabel(dateFrom, dateTo, format); let violationCount = current?.geom?.violationCount || 0; diff --git a/src/components/other/Icon.tsx b/src/components/other/Icon.tsx index ff4b62a..4fd91f1 100644 --- a/src/components/other/Icon.tsx +++ b/src/components/other/Icon.tsx @@ -20,7 +20,7 @@ import { BiWater } from "react-icons/bi"; import { BsLayersHalf, BsLink45Deg } from "react-icons/bs"; -import { CgMathMinus, CgMathPlus } from "react-icons/cg"; +import { CgMathMinus } from "react-icons/cg"; import { FaTrash } from "react-icons/fa"; import { FiClock, FiDownload, FiPhone, FiUser, FiUsers } from "react-icons/fi"; import { GiHamburgerMenu } from "react-icons/gi"; @@ -90,8 +90,6 @@ const Icon = ({ name, className }: IconProps) => { return ; case "verified": return ; - case "plus": - return ; case "minus": return ; case "search": diff --git a/src/components/other/MobileNavbar.tsx b/src/components/other/MobileNavbar.tsx index 76d61da..a5ee1f8 100644 --- a/src/components/other/MobileNavbar.tsx +++ b/src/components/other/MobileNavbar.tsx @@ -104,14 +104,6 @@ const SecondRow = styled.div` margin-bottom: 57px; `; -const Title = styled.div` - font-size: 2.3rem; - font-weight: bold; - - color: white; - margin-right: 11px; -`; - const BurgerIcon = styled(Icon)` cursor: pointer; font-size: 2rem; @@ -126,10 +118,4 @@ const ExitIcon = styled(Icon)` color: white; `; -const Hr = styled.div` - width: 50%; - margin: 10px 0 10px 0; - border-bottom: 1px solid #eeebe561; -`; - export default MobileNavbar; diff --git a/src/components/other/Table.tsx b/src/components/other/Table.tsx index adb5e7b..e5823c4 100644 --- a/src/components/other/Table.tsx +++ b/src/components/other/Table.tsx @@ -65,7 +65,14 @@ const Table = ({ })}` }); } - }, [searchParams, tableDataInfo, loading]); + }, [ + searchParams, + totalPages, + tableDataInfo, + loading, + navigate, + params?.page + ]); const generateTableContent = () => { if (!isEmpty(tableDataInfo.data)) { diff --git a/src/index.tsx b/src/index.tsx index 5e0b78d..74aaa16 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,15 @@ +import { useEffect } from "react"; import ReactDOM from "react-dom/client"; -import { BrowserRouter } from "react-router-dom"; +import { QueryClient, QueryClientProvider } from "react-query"; +import { + BrowserRouter, + createRoutesFromChildren, + matchRoutes, + useLocation, + useNavigationType +} from "react-router-dom"; import { ThemeProvider } from "styled-components"; +import * as Sentry from "@sentry/react"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; import * as serviceWorkerRegistration from "./serviceWorkerRegistration"; @@ -9,13 +18,39 @@ const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement ); +const queryClient = new QueryClient(); +const env = process.env; + +if (env.REACT_APP_SENTRY_DSN) { + Sentry.init({ + environment: env.REACT_APP_ENVIRONMENT, + dsn: env.REACT_APP_SENTRY_DSN, + integrations: [ + new Sentry.BrowserTracing({ + routingInstrumentation: Sentry.reactRouterV6Instrumentation( + useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes + ) + }) + ], + tracesSampleRate: 1, + release: env.REACT_APP_VERSION, + tracePropagationTargets: [env.REACT_APP_MAPS_HOST!] + }); +} + root.render( - - - - - - + + + + + + + + ); // If you want your app to work offline and load faster, you can change diff --git a/src/pages/HydroPowerPlant.tsx b/src/pages/HydroPowerPlant.tsx index b63ea40..4d0808f 100644 --- a/src/pages/HydroPowerPlant.tsx +++ b/src/pages/HydroPowerPlant.tsx @@ -1,149 +1,94 @@ import { isEmpty, isEqual } from "lodash"; -import moment from "moment"; -import { useEffect, useState } from "react"; -import { useParams, useSearchParams } from "react-router-dom"; +import { useState } from "react"; +import { useParams } from "react-router-dom"; import styled from "styled-components"; -import ButtonsGroup from "../components/buttons/ButtonsGroup"; -import Datepicker from "../components/fields/DatePicker"; +import EventTable from "../components/containers/EventsTable"; import DefaultLayout from "../components/Layouts/Default"; +import ButtonFilter from "../components/other/ButtonFilter"; import { Graphs } from "../components/other/Graphs"; +import HeaderInfo from "../components/other/HeaderInfo"; import Label from "../components/other/Label"; import LoaderComponent from "../components/other/LoaderComponent"; import TabBar, { Tab } from "../components/other/TabBar"; -import Table, { TableData } from "../components/other/Table"; import ViolationCountMessage from "../components/other/ViolationCountMessage"; import { device } from "../styles"; import { DateFormats, TimeRanges } from "../utils/constants"; import { - getCustomTimeRangeToQuery, - handlePagination, - handleViolationCount, - mapEvents, - timeRangeOptions, + getTimeRangeLabel, + handleGetViolationCount, timeRangeToQuery } from "../utils/functions"; import { useEventsByHydroPowerPlantId, useHydroPowerPlant } from "../utils/hooks"; -import { - descriptions, - eventLabels, - formLabels, - timeRangeLabels -} from "../utils/texts"; +import { formLabels } from "../utils/texts"; import { Range } from "../utils/types"; -const HydroPowerPlantGraphs = () => { - const tabs: Tab[] = [ - { value: "graph", label: "Grafikas" }, - { value: "table", label: "Lentelė" } - ]; - const format = DateFormats.DAY; - const [openCalendar, setOpenCalendar] = useState(false); - - const [timeFilter, setTimeFilter] = useState(TimeRanges.HOUR); - const [customDate, setCustomDate] = useState(timeRangeToQuery.hour); - - const dateFrom = customDate.time.$gte; - const dateTo = customDate.time.$lt; +enum TableValues { + GRAPH = "GRAPH", + TABLE = "TABLE" +} - const timeRangeLabel = `${moment(dateFrom).format(format)} - ${moment( - dateTo - ).format(format)}`; +const tabs: Tab[] = [ + { value: TableValues.GRAPH, label: "Grafikas" }, + { value: TableValues.TABLE, label: "Lentelė" } +]; +const HydroPowerPlantGraphs = () => { const { id } = useParams(); + const format = DateFormats.DAY; + const [timeFilter, setTimeFilter] = useState(TimeRanges.HOUR); + const [dateFilter, setDateFilter] = useState(timeRangeToQuery.hour); + const dateFrom = dateFilter.time.$gte; + const dateTo = dateFilter.time.$lt; + const timeRangeLabel = getTimeRangeLabel(dateFrom, dateTo, format); const [selectedTabValue, setSelectedTabValue] = useState(tabs[0].value); - const { hydroPowerPlant, loading } = useHydroPowerPlant(id!); + const { hydroPowerPlant, isLoading } = useHydroPowerPlant(id!); const { events, eventsLoading } = useEventsByHydroPowerPlantId( id!, - customDate + dateFilter ); - const [searchParams] = useSearchParams(); - const { page } = Object.fromEntries([...Array.from(searchParams)]); - - const [tableData, setTableData] = useState({ data: [] }); - - const handleSetTableData = () => { - if (!isEqual(selectedTabValue, "table")) return; - const pageData = handlePagination({ - data: events, - page: page, - pageSize: 40 - }); - setTableData({ - data: mapEvents({ ...hydroPowerPlant!, events: pageData.slicedData }), - totalPages: pageData.totalPages - }); + const fullHydroPowerPlant = { + ...hydroPowerPlant!, + events }; - useEffect(() => { - handleSetTableData(); - }, [page, loading, selectedTabValue, events]); - - const violationCount = handleViolationCount({ ...hydroPowerPlant!, events }); + const violationCount = handleGetViolationCount(fullHydroPowerPlant); const hasApi = !!hydroPowerPlant?.apiId; - if (loading) return ; - const renderValues = () => { + if (eventsLoading) return ; + if (!hasApi) return {formLabels.notReceivingData}; if (isEmpty(events)) return {formLabels.notFoundData}; - if (isEqual(selectedTabValue, "table")) + if (isEqual(selectedTabValue, TableValues.TABLE)) return ( -
); - return ( - - ); - }; - - const handleSetDateRange = (option: TimeRanges) => { - setTimeFilter(option); - if (isEqual(option, TimeRanges.OTHER_DAY)) { - return setOpenCalendar(true); - } - setCustomDate(timeRangeToQuery[option]); + return ; }; const renderContent = () => { - if (eventsLoading) return ; - return ( - - timeRangeLabels[option]} - onChange={(option) => { - handleSetDateRange(option); - }} - isSelected={(option) => isEqual(timeFilter, option)} - /> - { - setOpenCalendar(false); - }} - value={customDate.time.$gte} - open={openCalendar} - onChange={(date) => setCustomDate(getCustomTimeRangeToQuery(date!))} - /> - +
navigate(slugs.hydroPowerPlant(id))} - loading={loading} + loading={isLoading} tableDataInfo={tableData} labels={hydroPowerPlantsLabels} isFilterApplied={!!name} diff --git a/src/styles/index.ts b/src/styles/index.ts index dfa6747..9581c80 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -78,15 +78,12 @@ export const theme: Theme = { export const GlobalStyle = createGlobalStyle` *{ box-sizing: border-box; - font-family: Atkinson Hyperlegible; + font-family: "Roboto", sans-serif; } .leaflet-popup-content{ font-size:1.2rem } - - - html { font-size: 62.5%; width: 100vw; diff --git a/src/utils/hooks.ts b/src/utils/hooks.ts index 82823c8..c83eb30 100644 --- a/src/utils/hooks.ts +++ b/src/utils/hooks.ts @@ -49,7 +49,7 @@ export const useEventsByHydroPowerPlantId = (id: string, range: Range) => { ...range }); - const { data, isLoading } = useQuery( + const { data = [], isLoading } = useQuery( ["events", id, range], () => api.getEventsByHydroPowerPlantId({ query }), { From 15861811e03f9a9f0686b9ffa884f2dfceccd8e3 Mon Sep 17 00:00:00 2001 From: LWangllix Date: Thu, 10 Aug 2023 14:24:33 +0300 Subject: [PATCH 3/3] padding fix --- src/pages/HydroPowerPlantsTable.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/HydroPowerPlantsTable.tsx b/src/pages/HydroPowerPlantsTable.tsx index a7749a7..4aafb06 100644 --- a/src/pages/HydroPowerPlantsTable.tsx +++ b/src/pages/HydroPowerPlantsTable.tsx @@ -9,6 +9,7 @@ import TextField from "../components/fields/TextField"; import { getFilteredOptions } from "../components/fields/utils/functions"; import DefaultLayout from "../components/Layouts/Default"; import Table, { TableData } from "../components/other/Table"; +import { device } from "../styles"; import { handlePagination, mapHydro } from "../utils/functions"; import { useHydroPowerPlantsTable } from "../utils/hooks"; import { slugs } from "../utils/routes"; @@ -75,6 +76,9 @@ const Container = styled.div` margin: auto; padding: 20px 0 0 0; gap: 12px; + @media ${device.mobileL} { + padding: 20px; + } `; const FilterContainer = styled.div`