From e6912e2541fcfc7515f5b716aa897b9d1837ab7b Mon Sep 17 00:00:00 2001 From: evgeniywas Date: Sun, 27 Aug 2023 11:54:59 +0100 Subject: [PATCH 1/8] RC-10: add new custom hooks --- src/app/hooks/index.js | 23 +----- src/app/hooks/useOnClickOutside.js | 29 ++++++++ src/app/hooks/usePropertiesHeader.js | 105 +++++++++++++++++++++++++++ src/app/hooks/useWindowDimensions.js | 20 +++++ 4 files changed, 157 insertions(+), 20 deletions(-) create mode 100644 src/app/hooks/useOnClickOutside.js create mode 100644 src/app/hooks/usePropertiesHeader.js create mode 100644 src/app/hooks/useWindowDimensions.js diff --git a/src/app/hooks/index.js b/src/app/hooks/index.js index 68e6a53..4e6ba13 100644 --- a/src/app/hooks/index.js +++ b/src/app/hooks/index.js @@ -1,20 +1,3 @@ -import { useState, useEffect } from 'react'; - -export function useWindowDimensions() { - const getWindowDimensions = () => { - const { innerWidth: width, innerHeight: height } = window; - - return { width, height }; - }; - const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); - - useEffect(() => { - const onRecize = () => setWindowDimensions(getWindowDimensions()); - - window.addEventListener('resize', onRecize); - - return () => window.removeEventListener('resize', onRecize); - }, []); - - return windowDimensions; -} +export { default as useWindowDimensions } from './useWindowDimensions'; +export { default as useOnClickOutside } from './useOnClickOutside'; +export { default as usePropertiesHeader } from './usePropertiesHeader'; diff --git a/src/app/hooks/useOnClickOutside.js b/src/app/hooks/useOnClickOutside.js new file mode 100644 index 0000000..98cc268 --- /dev/null +++ b/src/app/hooks/useOnClickOutside.js @@ -0,0 +1,29 @@ +import { useEffect } from 'react'; + +export default function useOnClickOutside(ref, handler) { + useEffect(() => { + const onEvent = (event) => { + if (!ref.current) { + return; + } + + if (event instanceof KeyboardEvent && event.key === 'Escape') { + handler(event); + } else if (ref.current.contains(event.target)) { + return; + } + + handler(event); + }; + + document.addEventListener('mousedown', onEvent); + document.addEventListener('touchstart', onEvent); + document.addEventListener('keydown', onEvent); + + return () => { + document.removeEventListener('mousedown', onEvent); + document.removeEventListener('touchstart', onEvent); + document.removeEventListener('keydown', onEvent); + }; + }, [ref, handler]); +} diff --git a/src/app/hooks/usePropertiesHeader.js b/src/app/hooks/usePropertiesHeader.js new file mode 100644 index 0000000..6e3d4d1 --- /dev/null +++ b/src/app/hooks/usePropertiesHeader.js @@ -0,0 +1,105 @@ +import { PROPERTIES_LAYOUTS } from 'app/configs/consts'; +import { useState, useMemo, useCallback, useEffect } from 'react'; + +export default function usePropertiesHeader(items) { + const [categories, setCategories] = useState([ + { + name: 'all', + amount: 0, + active: true, + }, + ]); + const [layouts, setLayouts] = useState([ + { name: PROPERTIES_LAYOUTS.list, active: true }, + { name: PROPERTIES_LAYOUTS.grid, active: false }, + ]); + + const activeCategory = useMemo( + () => categories.find(({ active }) => active)?.name ?? categories[0].name, + [categories] + ); + const activeLayout = useMemo( + () => layouts.find(({ active }) => active)?.name ?? PROPERTIES_LAYOUTS.list, + [layouts] + ); + + const onCategory = useCallback( + (value) => { + if (value === activeCategory) { + return; + } + + setCategories((prevState) => + prevState.map((category) => ({ ...category, active: category.name === value })) + ); + }, + [activeCategory] + ); + + const onLayout = useCallback( + (value) => { + if (value === activeLayout) { + return; + } + + setLayouts((prevState) => prevState.map(({ name }) => ({ name, active: name === value }))); + }, + [activeLayout] + ); + + const onItemDelete = (itemCategory) => { + setCategories((prevState) => { + const isItemCategoryLast = + prevState.find((category) => category.name === itemCategory)?.amount === 1; + + return prevState + .map((category, idx) => { + if (!idx) { + return { + name: category.name, + amount: category.amount - 1, + active: isItemCategoryLast, + }; + } + + if (category?.name === itemCategory) { + if (category.amount > 1) { + return { ...category, amount: category.amount - 1 }; + } + + return null; + } + + return category; + }) + .filter((category) => category); + }); + }; + + useEffect(() => { + let updatedCategories = [...categories]; + items.forEach((item) => { + const hasItemCategory = updatedCategories.find((category) => category.name === item.category); + updatedCategories = updatedCategories.map((category, idx) => { + if (!idx) { + category.amount += 1; + } + + return category; + }); + + if (hasItemCategory) { + updatedCategories = updatedCategories.map((category) => ({ + ...category, + amount: item.category === category.name ? category.amount + 1 : category.amount, + })); + } else { + updatedCategories.push({ name: item.category, amount: 1, active: false }); + } + }); + setCategories(updatedCategories); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return { categories, activeCategory, onCategory, layouts, activeLayout, onLayout, onItemDelete }; +} diff --git a/src/app/hooks/useWindowDimensions.js b/src/app/hooks/useWindowDimensions.js new file mode 100644 index 0000000..8991ab0 --- /dev/null +++ b/src/app/hooks/useWindowDimensions.js @@ -0,0 +1,20 @@ +import { useState, useEffect } from 'react'; + +export default function useWindowDimensions() { + const getWindowDimensions = () => { + const { innerWidth: width, innerHeight: height } = window; + + return { width, height }; + }; + const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); + + useEffect(() => { + const onRecize = () => setWindowDimensions(getWindowDimensions()); + + window.addEventListener('resize', onRecize); + + return () => window.removeEventListener('resize', onRecize); + }, []); + + return windowDimensions; +} -- 2.20.1 From a5b30367cbc2220c25e5aa8b0e0ffe8f2d3a9ed2 Mon Sep 17 00:00:00 2001 From: evgeniywas Date: Sun, 27 Aug 2023 11:56:36 +0100 Subject: [PATCH 2/8] RC-10: create shared components for history and favorites pages --- src/app/main/home/components/AboutUs.js | 4 +- src/app/main/home/components/ArticleCard.js | 4 +- src/app/main/home/components/FeedbackForm.js | 2 +- .../main/home/components/StatisticsCard.js | 2 +- .../main/navigationPages/profile/Profile.js | 1 + .../shared-components/DateMark.js | 17 ++ .../shared-components/FavoriteButton.js | 21 +++ .../shared-components/MetaMark.js | 25 +++ .../shared-components/PropertiesHeader.js | 56 ++++++ .../shared-components/PropertyGridCard.js | 77 +++++++++ .../shared-components/PropertyListItem.js | 83 +++++++++ .../shared-components/StatisticsValue.js | 38 ++++ src/styles/print.css | 69 ++++---- src/styles/prism.css | 163 +++++++++--------- src/styles/tables.css | 54 +++--- 15 files changed, 467 insertions(+), 149 deletions(-) create mode 100644 src/app/main/navigationPages/shared-components/DateMark.js create mode 100644 src/app/main/navigationPages/shared-components/FavoriteButton.js create mode 100644 src/app/main/navigationPages/shared-components/MetaMark.js create mode 100644 src/app/main/navigationPages/shared-components/PropertiesHeader.js create mode 100644 src/app/main/navigationPages/shared-components/PropertyGridCard.js create mode 100644 src/app/main/navigationPages/shared-components/PropertyListItem.js create mode 100644 src/app/main/navigationPages/shared-components/StatisticsValue.js diff --git a/src/app/main/home/components/AboutUs.js b/src/app/main/home/components/AboutUs.js index 89bf520..42ade72 100644 --- a/src/app/main/home/components/AboutUs.js +++ b/src/app/main/home/components/AboutUs.js @@ -9,7 +9,7 @@ function AboutUs({ t }) {