From d5825c2b9923c7871c4f60f65bb57756ab7fe95a Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 15:04:23 +0300 Subject: [PATCH 01/42] fix: fix icons style in the header --- .../src/components/Header/Header.styled.ts | 2 +- .../frontend/src/components/Header/Header.tsx | 40 +++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/application/frontend/src/components/Header/Header.styled.ts b/application/frontend/src/components/Header/Header.styled.ts index 4b1964c..e333217 100644 --- a/application/frontend/src/components/Header/Header.styled.ts +++ b/application/frontend/src/components/Header/Header.styled.ts @@ -1,6 +1,6 @@ import styled from "styled-components" -import { Box } from "@mui/material" +import { Box, IconButton } from "@mui/material" export const Container = styled(Box)` display: flex; diff --git a/application/frontend/src/components/Header/Header.tsx b/application/frontend/src/components/Header/Header.tsx index 021e0bd..476be52 100644 --- a/application/frontend/src/components/Header/Header.tsx +++ b/application/frontend/src/components/Header/Header.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react" import { useTranslation } from "react-i18next" -import { AccountCircle, Message, MoreVert, Notifications } from "@mui/icons-material" +import { AccountCircleOutlined, MoreVertOutlined, NotificationsOutlined } from "@mui/icons-material" import { Button, IconButton, Switch, Tooltip, Typography } from "@mui/material" import { UserController } from "src/api/controllers/UserController" @@ -66,24 +66,40 @@ export const Header: React.FC = () => { - - - - - - - + + - - + + - - + + From 75267766d5858a7bd60a7671b386301c8bceae46 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 15:53:36 +0300 Subject: [PATCH 02/42] fix: use another component from the MUI library --- .../src/components/Header/Header.styled.ts | 2 +- .../components/NavButton/NavButton.styled.ts | 42 +++++++++---------- .../src/components/NavButton/NavButton.tsx | 9 ++-- .../frontend/src/pages/project/Project.tsx | 5 ++- .../frontend/src/pages/report/Report.tsx | 7 +++- 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/application/frontend/src/components/Header/Header.styled.ts b/application/frontend/src/components/Header/Header.styled.ts index e333217..4b1964c 100644 --- a/application/frontend/src/components/Header/Header.styled.ts +++ b/application/frontend/src/components/Header/Header.styled.ts @@ -1,6 +1,6 @@ import styled from "styled-components" -import { Box, IconButton } from "@mui/material" +import { Box } from "@mui/material" export const Container = styled(Box)` display: flex; diff --git a/application/frontend/src/components/NavButton/NavButton.styled.ts b/application/frontend/src/components/NavButton/NavButton.styled.ts index 8252a9c..26357c4 100644 --- a/application/frontend/src/components/NavButton/NavButton.styled.ts +++ b/application/frontend/src/components/NavButton/NavButton.styled.ts @@ -1,23 +1,23 @@ -import styled from "styled-components" +import Chip from "@mui/material/Chip" +import { emphasize, styled } from "@mui/material/styles" -import { Link } from "react-router-dom" - -export const StyledButton = styled(Link)` - display: inline-flex; - align-items: center; - justify-content: center; - background-color: rgba(0, 0, 0, 0.08); - gap: 4px; - border-radius: 50px; - padding: 4px 12px 4px 8px; - text-decoration: none; - color: ${(props) => props.theme.palette.text.primary}; - transition: - background-color 0.3s ease, - color 0.3s ease; - - &:hover { - background-color: rgba(0, 0, 0, 0.12); - color: ${(props) => props.theme.palette.text.secondary}; +export const StyledBreadcrumb = styled(Chip)(({ theme }) => { + const backgroundColor = + theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[800] + return { + backgroundColor, + height: theme.spacing(3), + color: theme.palette.text.primary, + fontWeight: theme.typography.fontWeightRegular, + display: "inline-flex", + whiteSpace: "nowrap", + maxWidth: "fit-content", + "&:hover, &:focus": { + backgroundColor: emphasize(backgroundColor, 0.06), + }, + "&:active": { + boxShadow: theme.shadows[1], + backgroundColor: emphasize(backgroundColor, 0.12), + }, } -` +}) as typeof Chip diff --git a/application/frontend/src/components/NavButton/NavButton.tsx b/application/frontend/src/components/NavButton/NavButton.tsx index f2fe754..8d8400c 100644 --- a/application/frontend/src/components/NavButton/NavButton.tsx +++ b/application/frontend/src/components/NavButton/NavButton.tsx @@ -1,14 +1,15 @@ -import React from "react" +import * as React from "react" -import { StyledButton } from "./NavButton.styled" +import { StyledBreadcrumb } from "./NavButton.styled" interface NavButtonProps { to: string children: React.ReactNode + icon?: React.ReactNode } -export const NavButton: React.FC = ({ to, children }) => { - return {children} +export const NavButton: React.FC = ({ to, children, icon }) => { + return } export default NavButton diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index f16d42c..b05cba8 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -1,6 +1,7 @@ import React from "react" import { Settings } from "@mui/icons-material" +import HomeIcon from "@mui/icons-material/Home" import { Typography } from "@mui/material" import { CreateProjectButton } from "src/components/CreateProjectButton" @@ -24,7 +25,9 @@ const projects = [ export const ProjectPage: React.FC = () => { return ( - Home + }> + Home + diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index 94cae44..76296f6 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react" import { Settings } from "@mui/icons-material" +import HomeIcon from "@mui/icons-material/Home" import { Container, Grid } from "@mui/material" import CreateReportButton from "src/components/CreateReportButton/CreateReportButton" @@ -105,8 +106,10 @@ export const ReportPage: React.FC = () => { return ( - Home/ - Проект название проекта + }> + Home + + /Проект название проекта
From 175e193b233b1686459c52ecf39e6151c4ffcc69 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 16:08:24 +0300 Subject: [PATCH 03/42] fix: fix backgraund color --- .../frontend/src/components/NavButton/NavButton.styled.ts | 4 ++-- application/frontend/src/components/Theme/Theme.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/frontend/src/components/NavButton/NavButton.styled.ts b/application/frontend/src/components/NavButton/NavButton.styled.ts index 26357c4..a02df54 100644 --- a/application/frontend/src/components/NavButton/NavButton.styled.ts +++ b/application/frontend/src/components/NavButton/NavButton.styled.ts @@ -2,8 +2,8 @@ import Chip from "@mui/material/Chip" import { emphasize, styled } from "@mui/material/styles" export const StyledBreadcrumb = styled(Chip)(({ theme }) => { - const backgroundColor = - theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[800] + const backgroundColor = "rgba(0, 0, 0, 0.08)" + return { backgroundColor, height: theme.spacing(3), diff --git a/application/frontend/src/components/Theme/Theme.tsx b/application/frontend/src/components/Theme/Theme.tsx index 87481d9..d315e51 100644 --- a/application/frontend/src/components/Theme/Theme.tsx +++ b/application/frontend/src/components/Theme/Theme.tsx @@ -38,7 +38,7 @@ export const Theme: React.FC = ({ children }) => { contrastText: "#fff", }, background: { - default: currentTheme === ThemeEnum.Light ? "#ffffff" : "#121212", + default: currentTheme === ThemeEnum.Light ? "rgba(250, 250, 250, 1)" : "#121212", paper: currentTheme === ThemeEnum.Light ? "#ffffff" : "#1e1e1e", }, text: { From c47d8e4ee5c332fdabd3ffa1b4c3742d52313c94 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 16:36:47 +0300 Subject: [PATCH 04/42] fix: use a different icon and font --- application/frontend/src/assets/settings.svg | 4 +++ .../src/pages/project/Project.styled.tsx | 26 +++++-------------- .../frontend/src/pages/project/Project.tsx | 11 +++----- .../frontend/src/pages/report/Report.tsx | 5 +--- 4 files changed, 15 insertions(+), 31 deletions(-) create mode 100644 application/frontend/src/assets/settings.svg diff --git a/application/frontend/src/assets/settings.svg b/application/frontend/src/assets/settings.svg new file mode 100644 index 0000000..7eaf886 --- /dev/null +++ b/application/frontend/src/assets/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/application/frontend/src/pages/project/Project.styled.tsx b/application/frontend/src/pages/project/Project.styled.tsx index 6dedc48..be73479 100644 --- a/application/frontend/src/pages/project/Project.styled.tsx +++ b/application/frontend/src/pages/project/Project.styled.tsx @@ -1,7 +1,5 @@ import styled from "styled-components" -import { Button } from "@mui/material" - export const Container = styled.div` padding: 16px; padding-left: 48px; @@ -25,24 +23,6 @@ export const TitleSection = styled.div` flex: 1; ` -export const SettingsButton = styled(Button)` - background-color: #007dff; - color: #ffffff; - width: 40px; - height: 40px; - border-radius: 8px; - min-width: 0; - padding: 0; - - &:hover { - background-color: #005bb5; - } - - svg { - font-size: 24px; - } -` - export const CreateProjectContainer = styled.div` display: flex; justify-content: flex-end; @@ -54,3 +34,9 @@ export const ProjectList = styled.div` flex-direction: column; gap: 16px; ` + +export const StyledTitle = styled.div` + font-size: 34px; + font-weight: 600; + color: rgba(29, 27, 32, 1); +` diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index b05cba8..3a1308f 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -1,19 +1,18 @@ import React from "react" -import { Settings } from "@mui/icons-material" import HomeIcon from "@mui/icons-material/Home" -import { Typography } from "@mui/material" +import settings from "src/assets/settings.svg" import { CreateProjectButton } from "src/components/CreateProjectButton" import { NavButton } from "src/components/NavButton" import { ProjectCard } from "src/components/ProjectCard" -import { SettingsButton } from "./Project.styled" import { Container, CreateProjectContainer, HeaderSection, ProjectList, + StyledTitle, TitleSection, } from "./Project.styled" @@ -30,10 +29,8 @@ export const ProjectPage: React.FC = () => { - - - - Список проектов + Settings + Список проектов diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index 76296f6..ebc90c5 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react" -import { Settings } from "@mui/icons-material" import HomeIcon from "@mui/icons-material/Home" import { Container, Grid } from "@mui/material" @@ -112,9 +111,7 @@ export const ReportPage: React.FC = () => { /Проект название проекта
- - - + <> Reports From 97db4db443886b2f9fe1aa234ab42f04d1bbfedb Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 16:43:01 +0300 Subject: [PATCH 05/42] feat: move the display change as a new component --- .../ViewModeToggle/ViewModeToggle.styled.ts | 0 .../ViewModeToggle/ViewModeToggle.tsx | 22 +++++++++++++++++++ .../src/components/ViewModeToggle/index.ts | 1 + 3 files changed, 23 insertions(+) create mode 100644 application/frontend/src/components/ViewModeToggle/ViewModeToggle.styled.ts create mode 100644 application/frontend/src/components/ViewModeToggle/ViewModeToggle.tsx create mode 100644 application/frontend/src/components/ViewModeToggle/index.ts diff --git a/application/frontend/src/components/ViewModeToggle/ViewModeToggle.styled.ts b/application/frontend/src/components/ViewModeToggle/ViewModeToggle.styled.ts new file mode 100644 index 0000000..e69de29 diff --git a/application/frontend/src/components/ViewModeToggle/ViewModeToggle.tsx b/application/frontend/src/components/ViewModeToggle/ViewModeToggle.tsx new file mode 100644 index 0000000..01a4b88 --- /dev/null +++ b/application/frontend/src/components/ViewModeToggle/ViewModeToggle.tsx @@ -0,0 +1,22 @@ +import React from "react" + +import ViewListIcon from "@mui/icons-material/ViewList" +import ViewModuleIcon from "@mui/icons-material/ViewModule" +import { IconButton } from "@mui/material" + +interface ViewModeToggleProps { + viewMode: "list" | "grid" + onToggleViewMode: () => void +} + +export const ViewModeToggle: React.FC = ({ viewMode, onToggleViewMode }) => { + return ( +
+ + {viewMode === "list" ? : } + +
+ ) +} + +export default ViewModeToggle diff --git a/application/frontend/src/components/ViewModeToggle/index.ts b/application/frontend/src/components/ViewModeToggle/index.ts new file mode 100644 index 0000000..f4d59a9 --- /dev/null +++ b/application/frontend/src/components/ViewModeToggle/index.ts @@ -0,0 +1 @@ +export { ViewModeToggle } from "./ViewModeToggle" From 0c77b499c8fe1b003981d1384c634a96c4b3e6bb Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 16:43:45 +0300 Subject: [PATCH 06/42] fix: remove display from component --- .../frontend/src/components/SearchBar/SearchBar.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/application/frontend/src/components/SearchBar/SearchBar.tsx b/application/frontend/src/components/SearchBar/SearchBar.tsx index 62ee405..fb1633f 100644 --- a/application/frontend/src/components/SearchBar/SearchBar.tsx +++ b/application/frontend/src/components/SearchBar/SearchBar.tsx @@ -1,11 +1,9 @@ import React from "react" import ClearIcon from "@mui/icons-material/Clear" -import ViewListIcon from "@mui/icons-material/ViewList" -import ViewModuleIcon from "@mui/icons-material/ViewModule" import { Grid, InputAdornment } from "@mui/material" -import { ClearButton, SearchContainer, SearchInput, ViewToggleButton } from "./SearchBar.styled" +import { ClearButton, SearchContainer, SearchInput } from "./SearchBar.styled" interface SearchBarProps { searchQuery: string @@ -18,8 +16,6 @@ interface SearchBarProps { export const SearchBar: React.FC = ({ searchQuery, onSearchChange, - viewMode, - onToggleViewMode, onClearSearch, }) => { return ( @@ -41,11 +37,6 @@ export const SearchBar: React.FC = ({ }} />
- - - {viewMode === "list" ? : } - - ) } From c5e404e6785b71f044ba4e7c0645b26a2b3e8e1d Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 16:44:02 +0300 Subject: [PATCH 07/42] fix: use new components --- application/frontend/src/pages/report/Report.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index ebc90c5..40f74dd 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -9,7 +9,7 @@ import ReportOverlay from "src/components/ReportOverlay/ReportOverlay" import ReportTimeline from "src/components/ReportTimeline/ReportTimeline" import ReportsTable from "src/components/ReportsTable/ReportsTable" import { SearchBar } from "src/components/SearchBar" -import { SettingsButton } from "src/pages/project/Project.styled" +import { ViewModeToggle } from "src/components/ViewModeToggle" import { Header, StyledContainer, Title } from "./Report.styled" @@ -121,10 +121,9 @@ export const ReportPage: React.FC = () => { + {" "} {viewMode === "list" ? ( ) : ( From 1338c3574fe7212a1da5394a99c658e3b7cbef07 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 17:25:18 +0300 Subject: [PATCH 08/42] fix: add outlined search bar --- .../components/SearchBar/SearchBar.styled.ts | 35 ++++++++++++++++++- .../src/components/SearchBar/SearchBar.tsx | 29 ++++++++++++--- .../frontend/src/pages/project/Project.tsx | 29 +++++++++++++-- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/application/frontend/src/components/SearchBar/SearchBar.styled.ts b/application/frontend/src/components/SearchBar/SearchBar.styled.ts index 50e5c51..4599a88 100644 --- a/application/frontend/src/components/SearchBar/SearchBar.styled.ts +++ b/application/frontend/src/components/SearchBar/SearchBar.styled.ts @@ -5,12 +5,16 @@ import { Grid, IconButton, TextField } from "@mui/material" export const SearchContainer = styled(Grid)` margin-bottom: 32px; align-items: center; + width: 100%; ` -export const SearchInput = styled(TextField)` +export const StandardSearchInput = styled(TextField)` width: 100%; .MuiInputBase-root { border-bottom: 1px solid #ccc; + display: flex; + align-items: center; + gap: 4px; &:hover { border-bottom: 1px solid #000; } @@ -26,6 +30,35 @@ export const SearchInput = styled(TextField)` } ` +export const OutlinedSearchInput = styled(TextField)` + width: 100%; + background-color: rgba(238, 238, 238, 1); + border-radius: 28px; + .MuiOutlinedInput-root { + padding: 4px; + display: flex; + align-items: center; + gap: 4px; + & fieldset { + border-color: transparent; + } + &:hover fieldset { + border-color: transparent; + } + &.Mui-focused fieldset { + border-color: transparent; + } + } +` + +export const InputAdornmentIcon = styled.div` + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; +` + export const ClearButton = styled(IconButton)` padding: 8px; color: #999; diff --git a/application/frontend/src/components/SearchBar/SearchBar.tsx b/application/frontend/src/components/SearchBar/SearchBar.tsx index fb1633f..ac26869 100644 --- a/application/frontend/src/components/SearchBar/SearchBar.tsx +++ b/application/frontend/src/components/SearchBar/SearchBar.tsx @@ -3,7 +3,13 @@ import React from "react" import ClearIcon from "@mui/icons-material/Clear" import { Grid, InputAdornment } from "@mui/material" -import { ClearButton, SearchContainer, SearchInput } from "./SearchBar.styled" +import { + ClearButton, + InputAdornmentIcon, + OutlinedSearchInput, + SearchContainer, + StandardSearchInput, +} from "./SearchBar.styled" interface SearchBarProps { searchQuery: string @@ -11,22 +17,35 @@ interface SearchBarProps { viewMode: "list" | "grid" | "timeline" onToggleViewMode: () => void onClearSearch: () => void + variant?: "standard" | "outlined" + startIcon?: React.ReactNode + placeholder?: string } export const SearchBar: React.FC = ({ searchQuery, onSearchChange, onClearSearch, + variant = "standard", + startIcon, + placeholder = "Search", }) => { + const InputComponent = variant === "outlined" ? OutlinedSearchInput : StandardSearchInput + return ( - - + + {startIcon} + + ) : null, endAdornment: searchQuery ? ( diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index 3a1308f..5d17815 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -1,11 +1,13 @@ -import React from "react" +import React, { useState } from "react" import HomeIcon from "@mui/icons-material/Home" +import SearchIcon from "@mui/icons-material/Search" import settings from "src/assets/settings.svg" import { CreateProjectButton } from "src/components/CreateProjectButton" import { NavButton } from "src/components/NavButton" import { ProjectCard } from "src/components/ProjectCard" +import { SearchBar } from "src/components/SearchBar" import { Container, @@ -22,6 +24,20 @@ const projects = [ ] export const ProjectPage: React.FC = () => { + const [searchQuery, setSearchQuery] = useState("") + + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value) + } + + const handleClearSearch = () => { + setSearchQuery("") + } + + const filteredProjects = projects.filter((project) => + project.name.toLowerCase().includes(searchQuery.toLowerCase()), + ) + return ( }> @@ -37,9 +53,16 @@ export const ProjectPage: React.FC = () => { console.log("Create Project")} /> - + } + placeholder="Search" + /> - {projects.map((project) => ( + {filteredProjects.map((project) => ( ))} From cb48ae700682a590607edf17981bd76021a5eda6 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 17:34:01 +0300 Subject: [PATCH 09/42] fix: Improve NavButton --- .../frontend/src/components/NavButton/NavButton.styled.ts | 8 +++++++- application/frontend/src/pages/project/Project.tsx | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/application/frontend/src/components/NavButton/NavButton.styled.ts b/application/frontend/src/components/NavButton/NavButton.styled.ts index a02df54..58b826c 100644 --- a/application/frontend/src/components/NavButton/NavButton.styled.ts +++ b/application/frontend/src/components/NavButton/NavButton.styled.ts @@ -8,9 +8,15 @@ export const StyledBreadcrumb = styled(Chip)(({ theme }) => { backgroundColor, height: theme.spacing(3), color: theme.palette.text.primary, - fontWeight: theme.typography.fontWeightRegular, + fontWeight: 400, + fontSize: "13px", + alignItems: "center", + justifyContent: "center", display: "inline-flex", whiteSpace: "nowrap", + padding: "4px 12px 4px 8px", + gap: "4px", + maxWidth: "fit-content", "&:hover, &:focus": { backgroundColor: emphasize(backgroundColor, 0.06), diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index 5d17815..2ab6194 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -40,8 +40,8 @@ export const ProjectPage: React.FC = () => { return ( - }> - Home + }> + Список проектов @@ -49,9 +49,9 @@ export const ProjectPage: React.FC = () => { Список проектов - + {/* console.log("Create Project")} /> - + */} Date: Wed, 21 Aug 2024 17:38:37 +0300 Subject: [PATCH 10/42] fix: change icon --- .../frontend/src/components/ProjectCard/ProjectCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/frontend/src/components/ProjectCard/ProjectCard.tsx b/application/frontend/src/components/ProjectCard/ProjectCard.tsx index 0fd4398..29adce0 100644 --- a/application/frontend/src/components/ProjectCard/ProjectCard.tsx +++ b/application/frontend/src/components/ProjectCard/ProjectCard.tsx @@ -1,6 +1,6 @@ import React from "react" -import { ArrowForwardIos } from "@mui/icons-material" +import ArrowForwardIcon from "@mui/icons-material/ArrowForward" import { ArrowIcon, Avatar, CardContainer, ProjectName } from "./ProjectCard.styled" @@ -18,7 +18,7 @@ export const ProjectCard: React.FC = ({ project }) => { {project.name} - + ) From 4e2f730b63119039919b6d68b4b01514089698a7 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 17:38:56 +0300 Subject: [PATCH 11/42] fix: change style for card --- .../src/components/ProjectCard/ProjectCard.styled.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/application/frontend/src/components/ProjectCard/ProjectCard.styled.ts b/application/frontend/src/components/ProjectCard/ProjectCard.styled.ts index 96d9c8a..7d3586c 100644 --- a/application/frontend/src/components/ProjectCard/ProjectCard.styled.ts +++ b/application/frontend/src/components/ProjectCard/ProjectCard.styled.ts @@ -8,10 +8,7 @@ export const CardContainer = styled.div` height: 80px; border-radius: 12px; background-color: #ffffff; - box-shadow: - 0px 1px 5px 0px rgba(0, 0, 0, 0.12), - 0px 2px 3px 0px rgba(0, 0, 0, 0.14), - 0px 3px 5px -2px rgba(0, 0, 0, 0.2); + border: 1px solid rgba(202, 196, 208, 1); cursor: pointer; &:hover { From 861f45b9f8066fb89f86d6ee2d4ddf2ba21a0014 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 17:52:06 +0300 Subject: [PATCH 12/42] feat: update indents --- .../src/pages/project/Project.styled.tsx | 11 ++++++- .../frontend/src/pages/project/Project.tsx | 30 +++++++++++-------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/application/frontend/src/pages/project/Project.styled.tsx b/application/frontend/src/pages/project/Project.styled.tsx index be73479..b7fa001 100644 --- a/application/frontend/src/pages/project/Project.styled.tsx +++ b/application/frontend/src/pages/project/Project.styled.tsx @@ -9,11 +9,16 @@ export const Container = styled.div` gap: 16px; ` +export const NavButtonContainer = styled.div` + margin-top: 39px; +` + export const HeaderSection = styled.div` display: flex; align-items: center; justify-content: space-between; gap: 16px; + margin-top: 49px; ` export const TitleSection = styled.div` @@ -26,13 +31,17 @@ export const TitleSection = styled.div` export const CreateProjectContainer = styled.div` display: flex; justify-content: flex-end; - margin-top: 16px; +` + +export const SearchBarContainer = styled.div` + margin-top: 37px; ` export const ProjectList = styled.div` display: flex; flex-direction: column; gap: 16px; + margin-top: 31px; ` export const StyledTitle = styled.div` diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index 2ab6194..0c7abd8 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -11,9 +11,10 @@ import { SearchBar } from "src/components/SearchBar" import { Container, - CreateProjectContainer, HeaderSection, + NavButtonContainer, ProjectList, + SearchBarContainer, StyledTitle, TitleSection, } from "./Project.styled" @@ -40,27 +41,30 @@ export const ProjectPage: React.FC = () => { return ( - }> - Список проектов - + + }> + Список проектов + + Settings Список проектов - {/* console.log("Create Project")} /> */} - } - placeholder="Search" - /> + + } + placeholder="Search" + /> + {filteredProjects.map((project) => ( From 2226d3cb5461e7b6e4e43c1a337c036d4f09fc86 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 18:04:37 +0300 Subject: [PATCH 13/42] fix: add stack icon and fix title style --- application/frontend/src/assets/stack.svg | 11 +++++++++++ application/frontend/src/pages/project/Project.tsx | 2 +- .../frontend/src/pages/report/Report.styled.tsx | 12 ++++++++++++ application/frontend/src/pages/report/Report.tsx | 12 ++++++++---- 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 application/frontend/src/assets/stack.svg diff --git a/application/frontend/src/assets/stack.svg b/application/frontend/src/assets/stack.svg new file mode 100644 index 0000000..66992f4 --- /dev/null +++ b/application/frontend/src/assets/stack.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index 0c7abd8..a216f82 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -4,7 +4,7 @@ import HomeIcon from "@mui/icons-material/Home" import SearchIcon from "@mui/icons-material/Search" import settings from "src/assets/settings.svg" -import { CreateProjectButton } from "src/components/CreateProjectButton" +// import { CreateProjectButton } from "src/components/CreateProjectButton" import { NavButton } from "src/components/NavButton" import { ProjectCard } from "src/components/ProjectCard" import { SearchBar } from "src/components/SearchBar" diff --git a/application/frontend/src/pages/report/Report.styled.tsx b/application/frontend/src/pages/report/Report.styled.tsx index 28ad500..cbdd534 100644 --- a/application/frontend/src/pages/report/Report.styled.tsx +++ b/application/frontend/src/pages/report/Report.styled.tsx @@ -27,3 +27,15 @@ export const HeaderRightContainer = styled.div` display: flex; align-items: center; ` + +export const TitleSection = styled.div` + display: flex; + align-items: center; + gap: 16px; + flex: 1; +` +export const StyledTitle = styled.div` + font-size: 34px; + font-weight: 600; + color: rgba(29, 27, 32, 1); +` diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index 40f74dd..064566d 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react" import HomeIcon from "@mui/icons-material/Home" import { Container, Grid } from "@mui/material" +import stack from "src/assets/stack.svg" import CreateReportButton from "src/components/CreateReportButton/CreateReportButton" import { NavButton } from "src/components/NavButton" import ReportOverlay from "src/components/ReportOverlay/ReportOverlay" @@ -11,7 +12,7 @@ import ReportsTable from "src/components/ReportsTable/ReportsTable" import { SearchBar } from "src/components/SearchBar" import { ViewModeToggle } from "src/components/ViewModeToggle" -import { Header, StyledContainer, Title } from "./Report.styled" +import { Header, StyledContainer, StyledTitle, Title, TitleSection } from "./Report.styled" const stageColors = { Initial: "rgba(0, 122, 255, 1)", @@ -105,14 +106,17 @@ export const ReportPage: React.FC = () => { return ( - }> + }> Home - /Проект название проекта + /Проект название проекта
<> - Reports + + Stack + Список проектов + From 5e87f644233bd9581fadd66838a184f0ea54e8ef Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 21 Aug 2024 18:26:22 +0300 Subject: [PATCH 14/42] fix: center elements --- .../src/pages/report/Report.styled.tsx | 9 ++++++++ .../frontend/src/pages/report/Report.tsx | 23 ++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/application/frontend/src/pages/report/Report.styled.tsx b/application/frontend/src/pages/report/Report.styled.tsx index cbdd534..89192f0 100644 --- a/application/frontend/src/pages/report/Report.styled.tsx +++ b/application/frontend/src/pages/report/Report.styled.tsx @@ -34,8 +34,17 @@ export const TitleSection = styled.div` gap: 16px; flex: 1; ` + export const StyledTitle = styled.div` font-size: 34px; font-weight: 600; color: rgba(29, 27, 32, 1); ` + +export const SearchViewContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + margin-bottom: 24px; +` diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index 064566d..7f82931 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -12,7 +12,13 @@ import ReportsTable from "src/components/ReportsTable/ReportsTable" import { SearchBar } from "src/components/SearchBar" import { ViewModeToggle } from "src/components/ViewModeToggle" -import { Header, StyledContainer, StyledTitle, Title, TitleSection } from "./Report.styled" +import { + Header, + SearchViewContainer, + StyledContainer, + StyledTitle, + TitleSection, +} from "./Report.styled" const stageColors = { Initial: "rgba(0, 122, 255, 1)", @@ -112,7 +118,6 @@ export const ReportPage: React.FC = () => { /Проект название проекта
- <> Stack Список проектов @@ -122,12 +127,14 @@ export const ReportPage: React.FC = () => {
- - {" "} + + + + {viewMode === "list" ? ( ) : ( From 86d964797098b4b00f93e993494de37ee517915d Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Sun, 25 Aug 2024 19:03:54 +0300 Subject: [PATCH 15/42] fix: change status chip color --- .../StatusChip/StatusChip.styled.ts | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/application/frontend/src/components/StatusChip/StatusChip.styled.ts b/application/frontend/src/components/StatusChip/StatusChip.styled.ts index 141e9f9..8112788 100644 --- a/application/frontend/src/components/StatusChip/StatusChip.styled.ts +++ b/application/frontend/src/components/StatusChip/StatusChip.styled.ts @@ -7,38 +7,53 @@ interface StatusChipProps { } export const StatusChip = styled(Chip)` - background-color: transparent !important; - border: 2px solid + background-color: ${({ status }) => + status === "Success" + ? "rgba(46, 125, 50, 0.04)" + : status === "Error" + ? "rgba(211, 47, 47, 0.04)" + : status === "Warning" + ? "rgba(239, 108, 0, 0.04)" + : "transparent"} !important; + border: 1px solid ${({ status }) => status === "Success" - ? "#4caf50" + ? "rgba(46, 125, 50, 1)" : status === "Error" - ? "#f44336" - : status === "In Progress" - ? "#ff9800" + ? "rgba(211, 47, 47, 1)" + : status === "Warning" + ? "rgba(239, 108, 0, 1)" : ""} !important; color: ${({ status }) => status === "Success" - ? "#4caf50" + ? "rgba(46, 125, 50, 1)" : status === "Error" - ? "#f44336" - : status === "In Progress" - ? "#ff9800" + ? "rgba(211, 47, 47, 1)" + : status === "Warning" + ? "rgba(239, 108, 0, 1)" : ""} !important; text-transform: uppercase; font-weight: bold; + .MuiChip-label { color: inherit; padding: 0 8px; } + + position: relative; + display: inline-flex; + align-items: center; + border-radius: 100px; + &:before { content: ""; - border: inherit; position: absolute; top: 0; left: 0; right: 0; bottom: 0; + border: inherit; + border-radius: inherit; pointer-events: none; } ` From 4d4d7ded5c543a7088db963a1c8093bbc4fbfe54 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Sun, 25 Aug 2024 19:04:59 +0300 Subject: [PATCH 16/42] fix: change borders in table --- .../ReportsTable/ReportsTable.styled.ts | 15 +++++++++++---- .../src/components/ReportsTable/ReportsTable.tsx | 5 +++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/application/frontend/src/components/ReportsTable/ReportsTable.styled.ts b/application/frontend/src/components/ReportsTable/ReportsTable.styled.ts index a1391bb..fc52a2d 100644 --- a/application/frontend/src/components/ReportsTable/ReportsTable.styled.ts +++ b/application/frontend/src/components/ReportsTable/ReportsTable.styled.ts @@ -18,19 +18,25 @@ export const AuthorAvatar = styled(Avatar)` export const StyledTableCell = styled(TableCell)` display: flex; align-items: center; - border-bottom: none; + padding: 16px; ` export const StyledTableRow = styled(TableRow)` - &:first-child ${StyledTableCell} { - border-bottom: 1px solid rgba(224, 224, 224, 1); + &:not(:first-of-type) ${StyledTableCell} { + border-top: 1px solid rgba(224, 224, 224, 1); } - &:not(:first-child) ${StyledTableCell} { + &:last-child ${StyledTableCell} { border-bottom: none; } ` +export const TableHeaderRow = styled(TableRow)` + ${StyledTableCell} { + border-bottom: 2px solid rgba(224, 224, 224, 1); + } +` + export const AuthorContainer = styled.div` display: flex; align-items: center; @@ -45,4 +51,5 @@ export const DateText = styled(Typography).attrs({ export const CustomTableContainer = styled(TableContainer)` box-shadow: none; + border: none; ` diff --git a/application/frontend/src/components/ReportsTable/ReportsTable.tsx b/application/frontend/src/components/ReportsTable/ReportsTable.tsx index 95ac9f5..0398f6b 100644 --- a/application/frontend/src/components/ReportsTable/ReportsTable.tsx +++ b/application/frontend/src/components/ReportsTable/ReportsTable.tsx @@ -13,6 +13,7 @@ import { ProjectName, StyledTableCell, StyledTableRow, + TableHeaderRow, } from "./ReportsTable.styled" export interface Report { @@ -34,13 +35,13 @@ export const ReportsTable: React.FC = ({ reports }) => { - + Name Status Author Start Date End Date - + {reports.map((report) => ( From 3a9aff96bac4bb492f7b5b0a671d93da095b0915 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Mon, 26 Aug 2024 14:39:14 +0300 Subject: [PATCH 17/42] fix: fix style for goals table --- .../GoalsTable/GoalsTable.styled.ts | 42 +++++++++ .../src/components/GoalsTable/GoalsTable.tsx | 85 +++++++++++-------- 2 files changed, 90 insertions(+), 37 deletions(-) diff --git a/application/frontend/src/components/GoalsTable/GoalsTable.styled.ts b/application/frontend/src/components/GoalsTable/GoalsTable.styled.ts index e69de29..92b331a 100644 --- a/application/frontend/src/components/GoalsTable/GoalsTable.styled.ts +++ b/application/frontend/src/components/GoalsTable/GoalsTable.styled.ts @@ -0,0 +1,42 @@ +import styled from "styled-components" + +import { Table, TableCell, TableRow, TextField } from "@mui/material" + +export const TableContainer = styled.div` + border-radius: 8px; + overflow: hidden; + border: 1px solid rgba(217, 217, 217, 1); +` + +export const StyledTable = styled(Table)` + border-collapse: separate; + border-spacing: 0; +` + +export const StyledTableCell = styled(TableCell)` + padding: 8px; + border: none; +` + +export const StyledTableRow = styled(TableRow)` + &:not(:last-child) ${StyledTableCell} { + border-bottom: 1px solid rgba(217, 217, 217, 1); + } +` + +export const CustomTextField = styled(TextField)` + .MuiInputBase-root { + border: none; + } + + .MuiInputLabel-root { + border: none; + } + + .MuiOutlinedInput-root { + border: none; + fieldset { + border: none; + } + } +` diff --git a/application/frontend/src/components/GoalsTable/GoalsTable.tsx b/application/frontend/src/components/GoalsTable/GoalsTable.tsx index ff7464c..d4499cf 100644 --- a/application/frontend/src/components/GoalsTable/GoalsTable.tsx +++ b/application/frontend/src/components/GoalsTable/GoalsTable.tsx @@ -1,8 +1,15 @@ import React from "react" -import { Table, TableBody, TableCell, TableHead, TableRow, TextField } from "@mui/material" +import { TableBody, TableHead, TableRow } from "@mui/material" import { StatusSelector } from "../StatusSelector" +import { + CustomTextField, + StyledTable, + StyledTableCell, + StyledTableRow, + TableContainer, +} from "./GoalsTable.styled" interface Goal { title: string @@ -17,43 +24,47 @@ interface GoalsTableProps { export const GoalsTable: React.FC = ({ goals, onGoalChange }) => { return ( -
- - - Название - Статус - Описание - - - - {goals.map((goal, index) => ( - - - onGoalChange(index, "title", e.target.value)} - /> - - - onGoalChange(index, "status", value)} - /> - - - onGoalChange(index, "description", e.target.value)} - /> - + + + + + Название + Статус + Описание - ))} - -
+ + + {goals.map((goal, index) => ( + + + onGoalChange(index, "title", e.target.value)} + variant="outlined" + /> + + + onGoalChange(index, "status", value)} + /> + + + onGoalChange(index, "description", e.target.value)} + variant="outlined" + /> + + + ))} + + + ) } From e14cae4527596b028d205f74574b29e932ca93ba Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Mon, 26 Aug 2024 14:39:58 +0300 Subject: [PATCH 18/42] fix: change status selector --- .../StatusSelector/StatusSelector.styled.ts | 4 +-- .../StatusSelector/StatusSelector.tsx | 27 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/application/frontend/src/components/StatusSelector/StatusSelector.styled.ts b/application/frontend/src/components/StatusSelector/StatusSelector.styled.ts index 4665cfb..af21f1d 100644 --- a/application/frontend/src/components/StatusSelector/StatusSelector.styled.ts +++ b/application/frontend/src/components/StatusSelector/StatusSelector.styled.ts @@ -2,8 +2,8 @@ import { Box } from "@mui/material" import { styled } from "@mui/system" export const StatusCircle = styled(Box)({ - width: 14, - height: 14, + width: 24, + height: 24, borderRadius: "50%", display: "inline-block", marginRight: 8, diff --git a/application/frontend/src/components/StatusSelector/StatusSelector.tsx b/application/frontend/src/components/StatusSelector/StatusSelector.tsx index 533afca..caebc82 100644 --- a/application/frontend/src/components/StatusSelector/StatusSelector.tsx +++ b/application/frontend/src/components/StatusSelector/StatusSelector.tsx @@ -1,6 +1,7 @@ import React from "react" -import { MenuItem, Select } from "@mui/material" +import ExpandMoreIcon from "@mui/icons-material/ExpandMore" +import { MenuItem, MenuProps, Select } from "@mui/material" import { StatusCircle, StyledFormControl } from "./StatusSelector.styled" @@ -17,14 +18,36 @@ export const StatusSelector: React.FC = ({ value, onChange { value: "None", color: "gray" }, ] + const MenuProps: Partial = { + PaperProps: { + sx: { + borderRadius: "50px", + padding: "0px 0px 8px 0px", + boxShadow: "0px 6px 30px 5px rgba(0, 0, 0, 0.12)", + }, + }, + } + return ( - Успех - Ошибка - В процессе + {t("reportOverlay.trafficLightColor")} +
@@ -87,7 +89,7 @@ export const ReportOverlay: React.FC = ({ isOpen, onClose }) = ({ isOpen, onClose }) = ({ isOpen, onClose }) - - + + @@ -122,20 +124,32 @@ export const ReportOverlay: React.FC = ({ isOpen, onClose }) <> - Цвета светофора + {t("reportOverlay.colors")} - + - + - + @@ -143,27 +157,27 @@ export const ReportOverlay: React.FC = ({ isOpen, onClose }) - Идет ли кто-то в отпуск? + {t("reportOverlay.onVacation")} - Идет ли в отпуск тот, кто пишет отчет? + {t("reportOverlay.reporterOnVacation")} @@ -175,7 +189,7 @@ export const ReportOverlay: React.FC = ({ isOpen, onClose }) <> @@ -188,7 +202,7 @@ export const ReportOverlay: React.FC = ({ isOpen, onClose }) diff --git a/application/frontend/src/components/ReportsTable/ReportsTable.tsx b/application/frontend/src/components/ReportsTable/ReportsTable.tsx index 0398f6b..6a9c0f9 100644 --- a/application/frontend/src/components/ReportsTable/ReportsTable.tsx +++ b/application/frontend/src/components/ReportsTable/ReportsTable.tsx @@ -1,6 +1,7 @@ import dayjs from "dayjs" import React from "react" +import { useTranslation } from "react-i18next" import { Table, TableBody, TableHead } from "@mui/material" @@ -31,16 +32,18 @@ interface ReportsTableProps { } export const ReportsTable: React.FC = ({ reports }) => { + const { t } = useTranslation() + return ( - Name - Status - Author - Start Date - End Date + {t("reportsTable.name")} + {t("reportsTable.status")} + {t("reportsTable.author")} + {t("reportsTable.startDate")} + {t("reportsTable.endDate")} diff --git a/application/frontend/src/components/StatusChip/StatusChip.tsx b/application/frontend/src/components/StatusChip/StatusChip.tsx index 88d6000..cb8652a 100644 --- a/application/frontend/src/components/StatusChip/StatusChip.tsx +++ b/application/frontend/src/components/StatusChip/StatusChip.tsx @@ -1,4 +1,5 @@ import React from "react" +import { useTranslation } from "react-i18next" import { StatusChip } from "./StatusChip.styled" @@ -7,7 +8,9 @@ interface StatusChipComponentProps { } export const StatusChipComponent: React.FC = ({ status }) => { - return + const { t } = useTranslation() + + return } export default StatusChipComponent diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index a216f82..4f1aca4 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -1,10 +1,10 @@ import React, { useState } from "react" +import { useTranslation } from "react-i18next" import HomeIcon from "@mui/icons-material/Home" import SearchIcon from "@mui/icons-material/Search" import settings from "src/assets/settings.svg" -// import { CreateProjectButton } from "src/components/CreateProjectButton" import { NavButton } from "src/components/NavButton" import { ProjectCard } from "src/components/ProjectCard" import { SearchBar } from "src/components/SearchBar" @@ -25,6 +25,8 @@ const projects = [ ] export const ProjectPage: React.FC = () => { + const { t } = useTranslation() + const [searchQuery, setSearchQuery] = useState("") const handleSearchChange = (e: React.ChangeEvent) => { @@ -42,14 +44,14 @@ export const ProjectPage: React.FC = () => { return ( - }> - Список проектов + }> + {t("projectList")} - Settings - Список проектов + {t("settings")} + {t("projectList")} {/* console.log("Create Project")} /> @@ -62,7 +64,7 @@ export const ProjectPage: React.FC = () => { onClearSearch={handleClearSearch} variant="outlined" startIcon={} - placeholder="Search" + placeholder={t("search")} /> diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index 4dedf97..b394b7e 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react" +import { useTranslation } from "react-i18next" import HomeIcon from "@mui/icons-material/Home" @@ -69,6 +70,7 @@ const reports: Report[] = [ ] export const ReportPage: React.FC = () => { + const { t } = useTranslation() const [searchQuery, setSearchQuery] = useState("") const [viewMode, setViewMode] = useState<"list" | "timeline">("list") const [isOverlayOpen, setOverlayOpen] = useState(false) @@ -112,15 +114,15 @@ export const ReportPage: React.FC = () => { }> - Список проектов + {t("projectList")} - Проект "Название проекта" + {t("projectName", { name: "Название проекта" })} - Stack - Список отчетов + {t("stackIconAlt")} + {t("reportList")} @@ -130,7 +132,7 @@ export const ReportPage: React.FC = () => { onSearchChange={handleSearchChange} onClearSearch={handleClearSearch} variant="standard" - placeholder="Search" + placeholder={t("searchPlaceholder")} style={{ flexGrow: 1, marginRight: "16px" }} /> From 14fc399dbda21dc50f34c509bb929c89f7acd74e Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Wed, 28 Aug 2024 17:48:45 +0300 Subject: [PATCH 25/42] fix: fix navigation --- application/frontend/src/pages/report/Report.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index b394b7e..e0c1028 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -113,7 +113,7 @@ export const ReportPage: React.FC = () => { - }> + }> {t("projectList")} {t("projectName", { name: "Название проекта" })} From ae7ca743ddb7a006f0ad123089a78786213d53c8 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Thu, 29 Aug 2024 18:31:35 +0300 Subject: [PATCH 26/42] feat: add login page --- .../frontend/src/pages/Home/Home.styles.ts | 50 +++++++++++++ application/frontend/src/pages/Home/Home.tsx | 75 ++++++++++++++++++- 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 application/frontend/src/pages/Home/Home.styles.ts diff --git a/application/frontend/src/pages/Home/Home.styles.ts b/application/frontend/src/pages/Home/Home.styles.ts new file mode 100644 index 0000000..716805a --- /dev/null +++ b/application/frontend/src/pages/Home/Home.styles.ts @@ -0,0 +1,50 @@ +import { Box, Card, Typography } from "@mui/material" +import { styled } from "@mui/material/styles" + +export const RootContainer = styled(Box)({ + display: "flex", + height: "100vh", + width: "100%", +}) + +export const ImageContainer = styled(Box)({ + width: "50%", + backgroundImage: `url(src/assets/loginpage.svg)`, + backgroundSize: "cover", + backgroundPosition: "center", +}) + +export const ContentContainer = styled(Box)({ + width: "50%", + padding: "20px", + display: "flex", + flexDirection: "column", +}) + +export const SearchContainer = styled(Box)({ + marginBottom: "20px", +}) + +export const ProfileCard = styled(Card)({ + marginBottom: "20px", +}) + +export const HeaderSection = styled(Box)({ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + gap: "16px", + marginTop: "49px", +}) + +export const TitleSection = styled(Box)({ + display: "flex", + alignItems: "center", + gap: "16px", + flex: 1, +}) + +export const StyledTitle = styled(Typography)({ + fontSize: "34px", + fontWeight: 600, +}) diff --git a/application/frontend/src/pages/Home/Home.tsx b/application/frontend/src/pages/Home/Home.tsx index 13664d7..6d2bc0d 100644 --- a/application/frontend/src/pages/Home/Home.tsx +++ b/application/frontend/src/pages/Home/Home.tsx @@ -1,3 +1,74 @@ -export const Home = () => { - return <>Welcome +import React, { useState } from "react" +import { useTranslation } from "react-i18next" + +import SearchIcon from "@mui/icons-material/Search" +import { CardContent, Grid, Typography } from "@mui/material" + +import { SearchBar } from "src/components/SearchBar" + +import { + ContentContainer, + HeaderSection, + ImageContainer, + ProfileCard, + RootContainer, + SearchContainer, + StyledTitle, + TitleSection, +} from "./Home.styles" + +export const Home: React.FC = () => { + const [searchQuery, setSearchQuery] = useState("") + const { t } = useTranslation() + + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value) + } + + const handleClearSearch = () => { + setSearchQuery("") + } + + return ( + + + + + + + {t("selectUser")} + + + + } + placeholder={t("search")} + /> + + + + {[1, 2, 3].map((profile) => ( + + + + + Профиль {profile} + + + Краткое описание профиля {profile}. + + + + + ))} + + + + ) } + +export default Home From 9f77ba398e5aef4bffb2d2c4adc5f62bc7ac70c7 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Thu, 29 Aug 2024 18:31:47 +0300 Subject: [PATCH 27/42] feat: add image --- application/frontend/src/assets/loginpage.svg | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 application/frontend/src/assets/loginpage.svg diff --git a/application/frontend/src/assets/loginpage.svg b/application/frontend/src/assets/loginpage.svg new file mode 100644 index 0000000..9e00c0f --- /dev/null +++ b/application/frontend/src/assets/loginpage.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 9a775a51dc9bba28de1e414f6718309dab22d659 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Thu, 29 Aug 2024 18:32:08 +0300 Subject: [PATCH 28/42] fix: change routes --- application/frontend/src/routes/routes.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/application/frontend/src/routes/routes.tsx b/application/frontend/src/routes/routes.tsx index b5bb57d..d321baa 100644 --- a/application/frontend/src/routes/routes.tsx +++ b/application/frontend/src/routes/routes.tsx @@ -1,17 +1,17 @@ import { RouteObject, useRoutes } from "react-router-dom" -import { ProjectPage } from "src/pages/project" -import { ReportPage } from "src/pages/report" - -import { Layout } from "../components/Layout/Layout" -import { Home } from "../pages/Home" +import { Layout } from "src/components/Layout/Layout" +import { Home } from "src/pages/Home" +import { ProjectPage } from "src/pages/Project" +import { ReportPage } from "src/pages/Report" const routes: RouteObject[] = [ { path: "/", element: , children: [ - { index: true, element: }, + { index: true, element: }, + { path: "project", element: }, { path: "reports", element: }, ], }, From a502a820eed6edeb4243921b9ad021550e29383b Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Thu, 29 Aug 2024 18:32:27 +0300 Subject: [PATCH 29/42] feat: add translate --- application/frontend/public/locales/translation/en.json | 3 ++- application/frontend/public/locales/translation/ru.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/application/frontend/public/locales/translation/en.json b/application/frontend/public/locales/translation/en.json index 7dbe681..07d2d7c 100644 --- a/application/frontend/public/locales/translation/en.json +++ b/application/frontend/public/locales/translation/en.json @@ -52,5 +52,6 @@ "colorValue": "Value", "yes":"Yes", "no":"No" - } + }, + "selectUser": "Select your user" } diff --git a/application/frontend/public/locales/translation/ru.json b/application/frontend/public/locales/translation/ru.json index 0a4a3b9..485b749 100644 --- a/application/frontend/public/locales/translation/ru.json +++ b/application/frontend/public/locales/translation/ru.json @@ -52,5 +52,6 @@ "colorValue": "Текст", "yes":"Да", "no":"Нет" - } + }, + "selectUser": "Выберите своего пользователя" } From 8d4a725101e02119d2e0235552cd4f11524722c4 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 20:44:13 +0300 Subject: [PATCH 30/42] feat: add profile card --- .../ProfileCard/ProfileCard.styled.ts | 34 +++++++++++++++++++ .../components/ProfileCard/ProfileCard.tsx | 27 +++++++++++++++ .../src/components/ProfileCard/index.ts | 1 + 3 files changed, 62 insertions(+) create mode 100644 application/frontend/src/components/ProfileCard/ProfileCard.styled.ts create mode 100644 application/frontend/src/components/ProfileCard/ProfileCard.tsx create mode 100644 application/frontend/src/components/ProfileCard/index.ts diff --git a/application/frontend/src/components/ProfileCard/ProfileCard.styled.ts b/application/frontend/src/components/ProfileCard/ProfileCard.styled.ts new file mode 100644 index 0000000..2869a15 --- /dev/null +++ b/application/frontend/src/components/ProfileCard/ProfileCard.styled.ts @@ -0,0 +1,34 @@ +import { Card } from "@mui/material" +import { styled } from "@mui/material/styles" + +export const ProfileCardContainer = styled(Card)({ + display: "flex", + alignItems: "center", + padding: "16px", + cursor: "pointer", + transition: "0.3s", + flexDirection: "row", + width: "100%", + "&:hover": { + boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.1)", + }, +}) + +export const Avatar = styled("img")({ + width: "50px", + height: "50px", + borderRadius: "50%", + marginRight: "16px", +}) + +export const ProfileName = styled("div")({ + flexGrow: 1, + fontSize: "18px", + fontWeight: "bold", +}) + +export const ArrowIcon = styled("div")({ + marginLeft: "auto", + display: "flex", + alignItems: "center", +}) diff --git a/application/frontend/src/components/ProfileCard/ProfileCard.tsx b/application/frontend/src/components/ProfileCard/ProfileCard.tsx new file mode 100644 index 0000000..defb4fd --- /dev/null +++ b/application/frontend/src/components/ProfileCard/ProfileCard.tsx @@ -0,0 +1,27 @@ +import React from "react" + +import ArrowForwardIcon from "@mui/icons-material/ArrowForward" + +import { ArrowIcon, Avatar, ProfileCardContainer, ProfileName } from "./ProfileCard.styled" + +interface ProfileCardProps { + profile: { + id: number + name: string + avatar: string + } +} + +export const ProfileCard: React.FC = ({ profile }) => { + return ( + + + {profile.name} + + + + + ) +} + +export default ProfileCard diff --git a/application/frontend/src/components/ProfileCard/index.ts b/application/frontend/src/components/ProfileCard/index.ts new file mode 100644 index 0000000..998fec5 --- /dev/null +++ b/application/frontend/src/components/ProfileCard/index.ts @@ -0,0 +1 @@ +export { ProfileCard } from "./ProfileCard" From d0aee2ac363ce0739df6fb473770e45b0500a74d Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 20:44:40 +0300 Subject: [PATCH 31/42] feat: add profile card component --- application/frontend/src/pages/Home/Home.tsx | 29 ++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/application/frontend/src/pages/Home/Home.tsx b/application/frontend/src/pages/Home/Home.tsx index 6d2bc0d..1e77472 100644 --- a/application/frontend/src/pages/Home/Home.tsx +++ b/application/frontend/src/pages/Home/Home.tsx @@ -2,15 +2,15 @@ import React, { useState } from "react" import { useTranslation } from "react-i18next" import SearchIcon from "@mui/icons-material/Search" -import { CardContent, Grid, Typography } from "@mui/material" +import { Box } from "@mui/material" +import { ProfileCard } from "src/components/ProfileCard" import { SearchBar } from "src/components/SearchBar" import { ContentContainer, HeaderSection, ImageContainer, - ProfileCard, RootContainer, SearchContainer, StyledTitle, @@ -29,6 +29,12 @@ export const Home: React.FC = () => { setSearchQuery("") } + const profiles = [ + { id: 1, name: "Профиль 1", avatar: "https://via.placeholder.com/50" }, + { id: 2, name: "Профиль 2", avatar: "https://via.placeholder.com/50" }, + { id: 3, name: "Профиль 3", avatar: "https://via.placeholder.com/50" }, + ] + return ( @@ -50,22 +56,11 @@ export const Home: React.FC = () => { /> - - {[1, 2, 3].map((profile) => ( - - - - - Профиль {profile} - - - Краткое описание профиля {profile}. - - - - + + {profiles.map((profile) => ( + ))} - + ) From 0d491b22c197065e4bb251610ecdf3e56ccca42d Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 20:57:26 +0300 Subject: [PATCH 32/42] fix: fix header display --- application/frontend/src/components/Layout/Layout.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/application/frontend/src/components/Layout/Layout.tsx b/application/frontend/src/components/Layout/Layout.tsx index 43ac703..7ac3d94 100644 --- a/application/frontend/src/components/Layout/Layout.tsx +++ b/application/frontend/src/components/Layout/Layout.tsx @@ -1,5 +1,5 @@ import { I18nextProvider } from "react-i18next" -import { Outlet } from "react-router-dom" +import { Outlet, useLocation } from "react-router-dom" import { Header } from "src/components/Header" import { Theme } from "src/components/Theme" @@ -8,13 +8,17 @@ import i18nInstance from "src/locales/service" import Markup from "./Layout.styled" export const Layout = () => { + const location = useLocation() + + const shouldShowHeader = location.pathname !== "/" + return ( -
+ {shouldShowHeader &&
} From c39761b595d723326ed1965055ec1413d29c0593 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 20:57:42 +0300 Subject: [PATCH 33/42] fix: change margin --- application/frontend/src/pages/Home/Home.styles.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/frontend/src/pages/Home/Home.styles.ts b/application/frontend/src/pages/Home/Home.styles.ts index 716805a..a4c03a3 100644 --- a/application/frontend/src/pages/Home/Home.styles.ts +++ b/application/frontend/src/pages/Home/Home.styles.ts @@ -22,7 +22,7 @@ export const ContentContainer = styled(Box)({ }) export const SearchContainer = styled(Box)({ - marginBottom: "20px", + marginBottom: "31px", }) export const ProfileCard = styled(Card)({ @@ -34,7 +34,7 @@ export const HeaderSection = styled(Box)({ alignItems: "center", justifyContent: "space-between", gap: "16px", - marginTop: "49px", + marginTop: "75px", }) export const TitleSection = styled(Box)({ @@ -42,6 +42,7 @@ export const TitleSection = styled(Box)({ alignItems: "center", gap: "16px", flex: 1, + marginBottom: "40px", }) export const StyledTitle = styled(Typography)({ From af05e8db18ea7bf89ee19334584c521cd13fe2a1 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 21:12:51 +0300 Subject: [PATCH 34/42] fix: add filter --- application/frontend/src/pages/Home/Home.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/application/frontend/src/pages/Home/Home.tsx b/application/frontend/src/pages/Home/Home.tsx index 1e77472..5cb1e28 100644 --- a/application/frontend/src/pages/Home/Home.tsx +++ b/application/frontend/src/pages/Home/Home.tsx @@ -30,11 +30,15 @@ export const Home: React.FC = () => { } const profiles = [ - { id: 1, name: "Профиль 1", avatar: "https://via.placeholder.com/50" }, - { id: 2, name: "Профиль 2", avatar: "https://via.placeholder.com/50" }, - { id: 3, name: "Профиль 3", avatar: "https://via.placeholder.com/50" }, + { id: 1, name: "Абоба", avatar: "" }, + { id: 2, name: "Бибоба", avatar: "" }, + { id: 3, name: "Вибоббв", avatar: "" }, ] + const filteredProfiles = profiles.filter((profile) => + profile.name.toLowerCase().includes(searchQuery.toLowerCase()), + ) + return ( @@ -57,7 +61,7 @@ export const Home: React.FC = () => { - {profiles.map((profile) => ( + {filteredProfiles.map((profile) => ( ))} From 2030f6aa0043d3ac171a1f4d5ab615488168634d Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 21:13:28 +0300 Subject: [PATCH 35/42] fix: add random backgraund color --- .../ProfileCard/ProfileCard.styled.ts | 10 ++++- .../components/ProfileCard/ProfileCard.tsx | 37 +++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/application/frontend/src/components/ProfileCard/ProfileCard.styled.ts b/application/frontend/src/components/ProfileCard/ProfileCard.styled.ts index 2869a15..2077fd1 100644 --- a/application/frontend/src/components/ProfileCard/ProfileCard.styled.ts +++ b/application/frontend/src/components/ProfileCard/ProfileCard.styled.ts @@ -14,12 +14,18 @@ export const ProfileCardContainer = styled(Card)({ }, }) -export const Avatar = styled("img")({ +export const Avatar = styled("div")<{ color: string }>(({ color }) => ({ width: "50px", height: "50px", borderRadius: "50%", marginRight: "16px", -}) + display: "flex", + alignItems: "center", + justifyContent: "center", + backgroundColor: color, + fontSize: "20px", + color: "#fff", +})) export const ProfileName = styled("div")({ flexGrow: 1, diff --git a/application/frontend/src/components/ProfileCard/ProfileCard.tsx b/application/frontend/src/components/ProfileCard/ProfileCard.tsx index defb4fd..33affb9 100644 --- a/application/frontend/src/components/ProfileCard/ProfileCard.tsx +++ b/application/frontend/src/components/ProfileCard/ProfileCard.tsx @@ -1,22 +1,53 @@ import React from "react" import ArrowForwardIcon from "@mui/icons-material/ArrowForward" +import { Box } from "@mui/material" import { ArrowIcon, Avatar, ProfileCardContainer, ProfileName } from "./ProfileCard.styled" +const getRandomColor = () => { + const colors = [ + "rgba(0, 145, 255, 1)", + "rgba(0, 100, 200, 1)", + "rgba(255, 140, 0, 1)", + "rgba(200, 150, 255, 1)", + "rgba(0, 200, 125, 1)", + "rgba(200, 200, 200, 1)", + "rgba(255, 255, 180, 1)", + "rgba(255, 180, 180, 1)", + ] + return colors[Math.floor(Math.random() * colors.length)] +} + interface ProfileCardProps { profile: { id: number name: string - avatar: string + avatar?: string } } export const ProfileCard: React.FC = ({ profile }) => { + const { name, avatar } = profile + const avatarLetter = name.charAt(0).toUpperCase() + const avatarColor = getRandomColor() + return ( - - {profile.name} + + {avatar ? ( + {name} + ) : ( + avatarLetter + )} + + + {name} + From c7ee957574d929a0391bcdb53ac128b462dd9a72 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 21:17:44 +0300 Subject: [PATCH 36/42] fix: fix project route --- application/frontend/src/routes/routes.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/frontend/src/routes/routes.tsx b/application/frontend/src/routes/routes.tsx index d321baa..beec392 100644 --- a/application/frontend/src/routes/routes.tsx +++ b/application/frontend/src/routes/routes.tsx @@ -11,7 +11,7 @@ const routes: RouteObject[] = [ element: , children: [ { index: true, element: }, - { path: "project", element: }, + { path: "projects", element: }, { path: "reports", element: }, ], }, From 1a417eb263d83795c260b90e32d15291ce40a5bc Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 21:18:03 +0300 Subject: [PATCH 37/42] feat: add navigate --- .../src/components/ProfileCard/ProfileCard.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/application/frontend/src/components/ProfileCard/ProfileCard.tsx b/application/frontend/src/components/ProfileCard/ProfileCard.tsx index 33affb9..31db2ad 100644 --- a/application/frontend/src/components/ProfileCard/ProfileCard.tsx +++ b/application/frontend/src/components/ProfileCard/ProfileCard.tsx @@ -1,11 +1,12 @@ import React from "react" +import { useNavigate } from "react-router-dom" import ArrowForwardIcon from "@mui/icons-material/ArrowForward" import { Box } from "@mui/material" import { ArrowIcon, Avatar, ProfileCardContainer, ProfileName } from "./ProfileCard.styled" -const getRandomColor = () => { +const getAvatarColor = (index: number) => { const colors = [ "rgba(0, 145, 255, 1)", "rgba(0, 100, 200, 1)", @@ -16,7 +17,7 @@ const getRandomColor = () => { "rgba(255, 255, 180, 1)", "rgba(255, 180, 180, 1)", ] - return colors[Math.floor(Math.random() * colors.length)] + return colors[index % colors.length] } interface ProfileCardProps { @@ -28,12 +29,17 @@ interface ProfileCardProps { } export const ProfileCard: React.FC = ({ profile }) => { - const { name, avatar } = profile + const { id, name, avatar } = profile const avatarLetter = name.charAt(0).toUpperCase() - const avatarColor = getRandomColor() + const avatarColor = getAvatarColor(id) + const navigate = useNavigate() + + const handleCardClick = () => { + navigate("/projects") + } return ( - + {avatar ? ( Date: Fri, 30 Aug 2024 21:26:23 +0300 Subject: [PATCH 38/42] feat: add gantt lib --- .../src/lib/calendar/calendar.module.css | 31 ++ .../frontend/src/lib/calendar/calendar.tsx | 395 ++++++++++++++ .../src/lib/calendar/top-part-of-calendar.tsx | 41 ++ .../frontend/src/lib/gantt/gantt.module.css | 21 + application/frontend/src/lib/gantt/gantt.tsx | 505 ++++++++++++++++++ .../src/lib/gantt/task-gantt-content.tsx | 302 +++++++++++ .../frontend/src/lib/gantt/task-gantt.tsx | 76 +++ .../frontend/src/lib/grid/grid-body.tsx | 127 +++++ .../frontend/src/lib/grid/grid.module.css | 15 + application/frontend/src/lib/grid/grid.tsx | 11 + application/frontend/src/lib/other/arrow.tsx | 106 ++++ .../lib/other/horizontal-scroll.module.css | 33 ++ .../src/lib/other/horizontal-scroll.tsx | 34 ++ .../frontend/src/lib/other/tooltip.module.css | 30 ++ .../frontend/src/lib/other/tooltip.tsx | 145 +++++ .../src/lib/other/vertical-scroll.module.css | 27 + .../src/lib/other/vertical-scroll.tsx | 41 ++ .../src/lib/task-item/bar/bar-date-handle.tsx | 32 ++ .../src/lib/task-item/bar/bar-display.tsx | 65 +++ .../lib/task-item/bar/bar-progress-handle.tsx | 19 + .../src/lib/task-item/bar/bar-small.tsx | 48 ++ .../src/lib/task-item/bar/bar.module.css | 21 + .../frontend/src/lib/task-item/bar/bar.tsx | 77 +++ .../task-item/milestone/milestone.module.css | 8 + .../src/lib/task-item/milestone/milestone.tsx | 37 ++ .../lib/task-item/project/project.module.css | 13 + .../src/lib/task-item/project/project.tsx | 74 +++ .../frontend/src/lib/task-item/task-item.tsx | 125 +++++ .../src/lib/task-item/task-list.module.css | 23 + .../lib/task-list/task-list-header.module.css | 23 + .../src/lib/task-list/task-list-header.tsx | 65 +++ .../lib/task-list/task-list-table.module.css | 38 ++ .../src/lib/task-list/task-list-table.tsx | 115 ++++ .../frontend/src/lib/task-list/task-list.tsx | 95 ++++ 34 files changed, 2818 insertions(+) create mode 100644 application/frontend/src/lib/calendar/calendar.module.css create mode 100644 application/frontend/src/lib/calendar/calendar.tsx create mode 100644 application/frontend/src/lib/calendar/top-part-of-calendar.tsx create mode 100644 application/frontend/src/lib/gantt/gantt.module.css create mode 100644 application/frontend/src/lib/gantt/gantt.tsx create mode 100644 application/frontend/src/lib/gantt/task-gantt-content.tsx create mode 100644 application/frontend/src/lib/gantt/task-gantt.tsx create mode 100644 application/frontend/src/lib/grid/grid-body.tsx create mode 100644 application/frontend/src/lib/grid/grid.module.css create mode 100644 application/frontend/src/lib/grid/grid.tsx create mode 100644 application/frontend/src/lib/other/arrow.tsx create mode 100644 application/frontend/src/lib/other/horizontal-scroll.module.css create mode 100644 application/frontend/src/lib/other/horizontal-scroll.tsx create mode 100644 application/frontend/src/lib/other/tooltip.module.css create mode 100644 application/frontend/src/lib/other/tooltip.tsx create mode 100644 application/frontend/src/lib/other/vertical-scroll.module.css create mode 100644 application/frontend/src/lib/other/vertical-scroll.tsx create mode 100644 application/frontend/src/lib/task-item/bar/bar-date-handle.tsx create mode 100644 application/frontend/src/lib/task-item/bar/bar-display.tsx create mode 100644 application/frontend/src/lib/task-item/bar/bar-progress-handle.tsx create mode 100644 application/frontend/src/lib/task-item/bar/bar-small.tsx create mode 100644 application/frontend/src/lib/task-item/bar/bar.module.css create mode 100644 application/frontend/src/lib/task-item/bar/bar.tsx create mode 100644 application/frontend/src/lib/task-item/milestone/milestone.module.css create mode 100644 application/frontend/src/lib/task-item/milestone/milestone.tsx create mode 100644 application/frontend/src/lib/task-item/project/project.module.css create mode 100644 application/frontend/src/lib/task-item/project/project.tsx create mode 100644 application/frontend/src/lib/task-item/task-item.tsx create mode 100644 application/frontend/src/lib/task-item/task-list.module.css create mode 100644 application/frontend/src/lib/task-list/task-list-header.module.css create mode 100644 application/frontend/src/lib/task-list/task-list-header.tsx create mode 100644 application/frontend/src/lib/task-list/task-list-table.module.css create mode 100644 application/frontend/src/lib/task-list/task-list-table.tsx create mode 100644 application/frontend/src/lib/task-list/task-list.tsx diff --git a/application/frontend/src/lib/calendar/calendar.module.css b/application/frontend/src/lib/calendar/calendar.module.css new file mode 100644 index 0000000..1be99d3 --- /dev/null +++ b/application/frontend/src/lib/calendar/calendar.module.css @@ -0,0 +1,31 @@ +.calendarBottomText { + text-anchor: middle; + fill: #333; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; +} + +.calendarTopTick { + stroke: #e6e4e4; +} + +.calendarTopText { + text-anchor: middle; + fill: #555; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; +} + +.calendarHeader { + fill: #ffffff; + stroke: #e0e0e0; + stroke-width: 1.4; +} diff --git a/application/frontend/src/lib/calendar/calendar.tsx b/application/frontend/src/lib/calendar/calendar.tsx new file mode 100644 index 0000000..a5860db --- /dev/null +++ b/application/frontend/src/lib/calendar/calendar.tsx @@ -0,0 +1,395 @@ +import React, { ReactChild } from "react"; +import { ViewMode } from "../../types/public-types"; +import { TopPartOfCalendar } from "./top-part-of-calendar"; +import { + getCachedDateTimeFormat, + getDaysInMonth, + getLocalDayOfWeek, + getLocaleMonth, + getWeekNumberISO8601, +} from "../../helpers/date-helper"; +import { DateSetup } from "../../types/date-setup"; +import styles from "./calendar.module.css"; + +export type CalendarProps = { + dateSetup: DateSetup; + locale: string; + viewMode: ViewMode; + rtl: boolean; + headerHeight: number; + columnWidth: number; + fontFamily: string; + fontSize: string; +}; + +export const Calendar: React.FC = ({ + dateSetup, + locale, + viewMode, + rtl, + headerHeight, + columnWidth, + fontFamily, + fontSize, +}) => { + const getCalendarValuesForYear = () => { + const topValues: ReactChild[] = []; + const bottomValues: ReactChild[] = []; + const topDefaultHeight = headerHeight * 0.5; + for (let i = 0; i < dateSetup.dates.length; i++) { + const date = dateSetup.dates[i]; + const bottomValue = date.getFullYear(); + bottomValues.push( + + {bottomValue} + + ); + if ( + i === 0 || + date.getFullYear() !== dateSetup.dates[i - 1].getFullYear() + ) { + const topValue = date.getFullYear().toString(); + let xText: number; + if (rtl) { + xText = (6 + i + date.getFullYear() + 1) * columnWidth; + } else { + xText = (6 + i - date.getFullYear()) * columnWidth; + } + topValues.push( + + ); + } + } + return [topValues, bottomValues]; + }; + + const getCalendarValuesForQuarterYear = () => { + const topValues: ReactChild[] = []; + const bottomValues: ReactChild[] = []; + const topDefaultHeight = headerHeight * 0.5; + for (let i = 0; i < dateSetup.dates.length; i++) { + const date = dateSetup.dates[i]; + // const bottomValue = getLocaleMonth(date, locale); + const quarter = "Q" + Math.floor((date.getMonth() + 3) / 3); + bottomValues.push( + + {quarter} + + ); + if ( + i === 0 || + date.getFullYear() !== dateSetup.dates[i - 1].getFullYear() + ) { + const topValue = date.getFullYear().toString(); + let xText: number; + if (rtl) { + xText = (6 + i + date.getMonth() + 1) * columnWidth; + } else { + xText = (6 + i - date.getMonth()) * columnWidth; + } + topValues.push( + + ); + } + } + return [topValues, bottomValues]; + }; + + const getCalendarValuesForMonth = () => { + const topValues: ReactChild[] = []; + const bottomValues: ReactChild[] = []; + const topDefaultHeight = headerHeight * 0.5; + for (let i = 0; i < dateSetup.dates.length; i++) { + const date = dateSetup.dates[i]; + const bottomValue = getLocaleMonth(date, locale); + bottomValues.push( + + {bottomValue} + + ); + if ( + i === 0 || + date.getFullYear() !== dateSetup.dates[i - 1].getFullYear() + ) { + const topValue = date.getFullYear().toString(); + let xText: number; + if (rtl) { + xText = (6 + i + date.getMonth() + 1) * columnWidth; + } else { + xText = (6 + i - date.getMonth()) * columnWidth; + } + topValues.push( + + ); + } + } + return [topValues, bottomValues]; + }; + + const getCalendarValuesForWeek = () => { + const topValues: ReactChild[] = []; + const bottomValues: ReactChild[] = []; + let weeksCount: number = 1; + const topDefaultHeight = headerHeight * 0.5; + const dates = dateSetup.dates; + for (let i = dates.length - 1; i >= 0; i--) { + const date = dates[i]; + let topValue = ""; + if (i === 0 || date.getMonth() !== dates[i - 1].getMonth()) { + // top + topValue = `${getLocaleMonth(date, locale)}, ${date.getFullYear()}`; + } + // bottom + const bottomValue = `W${getWeekNumberISO8601(date)}`; + + bottomValues.push( + + {bottomValue} + + ); + + if (topValue) { + // if last day is new month + if (i !== dates.length - 1) { + topValues.push( + + ); + } + weeksCount = 0; + } + weeksCount++; + } + return [topValues, bottomValues]; + }; + + const getCalendarValuesForDay = () => { + const topValues: ReactChild[] = []; + const bottomValues: ReactChild[] = []; + const topDefaultHeight = headerHeight * 0.5; + const dates = dateSetup.dates; + for (let i = 0; i < dates.length; i++) { + const date = dates[i]; + const bottomValue = `${getLocalDayOfWeek(date, locale, "short")}, ${date + .getDate() + .toString()}`; + + bottomValues.push( + + {bottomValue} + + ); + if ( + i + 1 !== dates.length && + date.getMonth() !== dates[i + 1].getMonth() + ) { + const topValue = getLocaleMonth(date, locale); + + topValues.push( + + ); + } + } + return [topValues, bottomValues]; + }; + + const getCalendarValuesForPartOfDay = () => { + const topValues: ReactChild[] = []; + const bottomValues: ReactChild[] = []; + const ticks = viewMode === ViewMode.HalfDay ? 2 : 4; + const topDefaultHeight = headerHeight * 0.5; + const dates = dateSetup.dates; + for (let i = 0; i < dates.length; i++) { + const date = dates[i]; + const bottomValue = getCachedDateTimeFormat(locale, { + hour: "numeric", + }).format(date); + + bottomValues.push( + + {bottomValue} + + ); + if (i === 0 || date.getDate() !== dates[i - 1].getDate()) { + const topValue = `${getLocalDayOfWeek( + date, + locale, + "short" + )}, ${date.getDate()} ${getLocaleMonth(date, locale)}`; + topValues.push( + + ); + } + } + + return [topValues, bottomValues]; + }; + + const getCalendarValuesForHour = () => { + const topValues: ReactChild[] = []; + const bottomValues: ReactChild[] = []; + const topDefaultHeight = headerHeight * 0.5; + const dates = dateSetup.dates; + for (let i = 0; i < dates.length; i++) { + const date = dates[i]; + const bottomValue = getCachedDateTimeFormat(locale, { + hour: "numeric", + }).format(date); + + bottomValues.push( + + {bottomValue} + + ); + if (i !== 0 && date.getDate() !== dates[i - 1].getDate()) { + const displayDate = dates[i - 1]; + const topValue = `${getLocalDayOfWeek( + displayDate, + locale, + "long" + )}, ${displayDate.getDate()} ${getLocaleMonth(displayDate, locale)}`; + const topPosition = (date.getHours() - 24) / 2; + topValues.push( + + ); + } + } + + return [topValues, bottomValues]; + }; + + let topValues: ReactChild[] = []; + let bottomValues: ReactChild[] = []; + switch (dateSetup.viewMode) { + case ViewMode.Year: + [topValues, bottomValues] = getCalendarValuesForYear(); + break; + case ViewMode.QuarterYear: + [topValues, bottomValues] = getCalendarValuesForQuarterYear(); + break; + case ViewMode.Month: + [topValues, bottomValues] = getCalendarValuesForMonth(); + break; + case ViewMode.Week: + [topValues, bottomValues] = getCalendarValuesForWeek(); + break; + case ViewMode.Day: + [topValues, bottomValues] = getCalendarValuesForDay(); + break; + case ViewMode.QuarterDay: + case ViewMode.HalfDay: + [topValues, bottomValues] = getCalendarValuesForPartOfDay(); + break; + case ViewMode.Hour: + [topValues, bottomValues] = getCalendarValuesForHour(); + } + return ( + + + {bottomValues} {topValues} + + ); +}; diff --git a/application/frontend/src/lib/calendar/top-part-of-calendar.tsx b/application/frontend/src/lib/calendar/top-part-of-calendar.tsx new file mode 100644 index 0000000..d24f376 --- /dev/null +++ b/application/frontend/src/lib/calendar/top-part-of-calendar.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import styles from "./calendar.module.css"; + +type TopPartOfCalendarProps = { + value: string; + x1Line: number; + y1Line: number; + y2Line: number; + xText: number; + yText: number; +}; + +export const TopPartOfCalendar: React.FC = ({ + value, + x1Line, + y1Line, + y2Line, + xText, + yText, +}) => { + return ( + + + + {value} + + + ); +}; diff --git a/application/frontend/src/lib/gantt/gantt.module.css b/application/frontend/src/lib/gantt/gantt.module.css new file mode 100644 index 0000000..8169a19 --- /dev/null +++ b/application/frontend/src/lib/gantt/gantt.module.css @@ -0,0 +1,21 @@ +.ganttVerticalContainer { + overflow: hidden; + font-size: 0; + margin: 0; + padding: 0; +} + +.horizontalContainer { + margin: 0; + padding: 0; + overflow: hidden; +} + +.wrapper { + display: flex; + padding: 0; + margin: 0; + list-style: none; + outline: none; + position: relative; +} diff --git a/application/frontend/src/lib/gantt/gantt.tsx b/application/frontend/src/lib/gantt/gantt.tsx new file mode 100644 index 0000000..b90483f --- /dev/null +++ b/application/frontend/src/lib/gantt/gantt.tsx @@ -0,0 +1,505 @@ +import React, { + useState, + SyntheticEvent, + useRef, + useEffect, + useMemo, +} from "react"; +import { ViewMode, GanttProps, Task } from "../../types/public-types"; +import { GridProps } from "../grid/grid"; +import { ganttDateRange, seedDates } from "../../helpers/date-helper"; +import { CalendarProps } from "../calendar/calendar"; +import { TaskGanttContentProps } from "./task-gantt-content"; +import { TaskListHeaderDefault } from "../task-list/task-list-header"; +import { TaskListTableDefault } from "../task-list/task-list-table"; +import { StandardTooltipContent, Tooltip } from "../other/tooltip"; +import { VerticalScroll } from "../other/vertical-scroll"; +import { TaskListProps, TaskList } from "../task-list/task-list"; +import { TaskGantt } from "./task-gantt"; +import { BarTask } from "../../types/bar-task"; +import { convertToBarTasks } from "../../helpers/bar-helper"; +import { GanttEvent } from "../../types/gantt-task-actions"; +import { DateSetup } from "../../types/date-setup"; +import { HorizontalScroll } from "../other/horizontal-scroll"; +import { removeHiddenTasks, sortTasks } from "../../helpers/other-helper"; +import styles from "./gantt.module.css"; + +export const Gantt: React.FunctionComponent = ({ + tasks, + headerHeight = 50, + columnWidth = 60, + listCellWidth = "155px", + rowHeight = 50, + ganttHeight = 0, + viewMode = ViewMode.Day, + preStepsCount = 1, + locale = "en-GB", + barFill = 60, + barCornerRadius = 3, + barProgressColor = "#a3a3ff", + barProgressSelectedColor = "#8282f5", + barBackgroundColor = "#b8c2cc", + barBackgroundSelectedColor = "#aeb8c2", + projectProgressColor = "#7db59a", + projectProgressSelectedColor = "#59a985", + projectBackgroundColor = "#fac465", + projectBackgroundSelectedColor = "#f7bb53", + milestoneBackgroundColor = "#f1c453", + milestoneBackgroundSelectedColor = "#f29e4c", + rtl = false, + handleWidth = 8, + timeStep = 300000, + arrowColor = "grey", + fontFamily = "Arial, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue", + fontSize = "14px", + arrowIndent = 20, + todayColor = "rgba(252, 248, 227, 0.5)", + viewDate, + TooltipContent = StandardTooltipContent, + TaskListHeader = TaskListHeaderDefault, + TaskListTable = TaskListTableDefault, + onDateChange, + onProgressChange, + onDoubleClick, + onClick, + onDelete, + onSelect, + onExpanderClick, +}) => { + const wrapperRef = useRef(null); + const taskListRef = useRef(null); + const [dateSetup, setDateSetup] = useState(() => { + const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount); + return { viewMode, dates: seedDates(startDate, endDate, viewMode) }; + }); + const [currentViewDate, setCurrentViewDate] = useState( + undefined + ); + + const [taskListWidth, setTaskListWidth] = useState(0); + const [svgContainerWidth, setSvgContainerWidth] = useState(0); + const [svgContainerHeight, setSvgContainerHeight] = useState(ganttHeight); + const [barTasks, setBarTasks] = useState([]); + const [ganttEvent, setGanttEvent] = useState({ + action: "", + }); + const taskHeight = useMemo( + () => (rowHeight * barFill) / 100, + [rowHeight, barFill] + ); + + const [selectedTask, setSelectedTask] = useState(); + const [failedTask, setFailedTask] = useState(null); + + const svgWidth = dateSetup.dates.length * columnWidth; + const ganttFullHeight = barTasks.length * rowHeight; + + const [scrollY, setScrollY] = useState(0); + const [scrollX, setScrollX] = useState(-1); + const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false); + + // task change events + useEffect(() => { + let filteredTasks: Task[]; + if (onExpanderClick) { + filteredTasks = removeHiddenTasks(tasks); + } else { + filteredTasks = tasks; + } + filteredTasks = filteredTasks.sort(sortTasks); + const [startDate, endDate] = ganttDateRange( + filteredTasks, + viewMode, + preStepsCount + ); + let newDates = seedDates(startDate, endDate, viewMode); + if (rtl) { + newDates = newDates.reverse(); + if (scrollX === -1) { + setScrollX(newDates.length * columnWidth); + } + } + setDateSetup({ dates: newDates, viewMode }); + setBarTasks( + convertToBarTasks( + filteredTasks, + newDates, + columnWidth, + rowHeight, + taskHeight, + barCornerRadius, + handleWidth, + rtl, + barProgressColor, + barProgressSelectedColor, + barBackgroundColor, + barBackgroundSelectedColor, + projectProgressColor, + projectProgressSelectedColor, + projectBackgroundColor, + projectBackgroundSelectedColor, + milestoneBackgroundColor, + milestoneBackgroundSelectedColor + ) + ); + }, [ + tasks, + viewMode, + preStepsCount, + rowHeight, + barCornerRadius, + columnWidth, + taskHeight, + handleWidth, + barProgressColor, + barProgressSelectedColor, + barBackgroundColor, + barBackgroundSelectedColor, + projectProgressColor, + projectProgressSelectedColor, + projectBackgroundColor, + projectBackgroundSelectedColor, + milestoneBackgroundColor, + milestoneBackgroundSelectedColor, + rtl, + scrollX, + onExpanderClick, + ]); + + useEffect(() => { + if ( + viewMode === dateSetup.viewMode && + ((viewDate && !currentViewDate) || + (viewDate && currentViewDate?.valueOf() !== viewDate.valueOf())) + ) { + const dates = dateSetup.dates; + const index = dates.findIndex( + (d, i) => + viewDate.valueOf() >= d.valueOf() && + i + 1 !== dates.length && + viewDate.valueOf() < dates[i + 1].valueOf() + ); + if (index === -1) { + return; + } + setCurrentViewDate(viewDate); + setScrollX(columnWidth * index); + } + }, [ + viewDate, + columnWidth, + dateSetup.dates, + dateSetup.viewMode, + viewMode, + currentViewDate, + setCurrentViewDate, + ]); + + useEffect(() => { + const { changedTask, action } = ganttEvent; + if (changedTask) { + if (action === "delete") { + setGanttEvent({ action: "" }); + setBarTasks(barTasks.filter(t => t.id !== changedTask.id)); + } else if ( + action === "move" || + action === "end" || + action === "start" || + action === "progress" + ) { + const prevStateTask = barTasks.find(t => t.id === changedTask.id); + if ( + prevStateTask && + (prevStateTask.start.getTime() !== changedTask.start.getTime() || + prevStateTask.end.getTime() !== changedTask.end.getTime() || + prevStateTask.progress !== changedTask.progress) + ) { + // actions for change + const newTaskList = barTasks.map(t => + t.id === changedTask.id ? changedTask : t + ); + setBarTasks(newTaskList); + } + } + } + }, [ganttEvent, barTasks]); + + useEffect(() => { + if (failedTask) { + setBarTasks(barTasks.map(t => (t.id !== failedTask.id ? t : failedTask))); + setFailedTask(null); + } + }, [failedTask, barTasks]); + + useEffect(() => { + if (!listCellWidth) { + setTaskListWidth(0); + } + if (taskListRef.current) { + setTaskListWidth(taskListRef.current.offsetWidth); + } + }, [taskListRef, listCellWidth]); + + useEffect(() => { + if (wrapperRef.current) { + setSvgContainerWidth(wrapperRef.current.offsetWidth - taskListWidth); + } + }, [wrapperRef, taskListWidth]); + + useEffect(() => { + if (ganttHeight) { + setSvgContainerHeight(ganttHeight + headerHeight); + } else { + setSvgContainerHeight(tasks.length * rowHeight + headerHeight); + } + }, [ganttHeight, tasks, headerHeight, rowHeight]); + + // scroll events + useEffect(() => { + const handleWheel = (event: WheelEvent) => { + if (event.shiftKey || event.deltaX) { + const scrollMove = event.deltaX ? event.deltaX : event.deltaY; + let newScrollX = scrollX + scrollMove; + if (newScrollX < 0) { + newScrollX = 0; + } else if (newScrollX > svgWidth) { + newScrollX = svgWidth; + } + setScrollX(newScrollX); + event.preventDefault(); + } else if (ganttHeight) { + let newScrollY = scrollY + event.deltaY; + if (newScrollY < 0) { + newScrollY = 0; + } else if (newScrollY > ganttFullHeight - ganttHeight) { + newScrollY = ganttFullHeight - ganttHeight; + } + if (newScrollY !== scrollY) { + setScrollY(newScrollY); + event.preventDefault(); + } + } + + setIgnoreScrollEvent(true); + }; + + // subscribe if scroll is necessary + wrapperRef.current?.addEventListener("wheel", handleWheel, { + passive: false, + }); + return () => { + wrapperRef.current?.removeEventListener("wheel", handleWheel); + }; + }, [ + wrapperRef, + scrollY, + scrollX, + ganttHeight, + svgWidth, + rtl, + ganttFullHeight, + ]); + + const handleScrollY = (event: SyntheticEvent) => { + if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) { + setScrollY(event.currentTarget.scrollTop); + setIgnoreScrollEvent(true); + } else { + setIgnoreScrollEvent(false); + } + }; + + const handleScrollX = (event: SyntheticEvent) => { + if (scrollX !== event.currentTarget.scrollLeft && !ignoreScrollEvent) { + setScrollX(event.currentTarget.scrollLeft); + setIgnoreScrollEvent(true); + } else { + setIgnoreScrollEvent(false); + } + }; + + /** + * Handles arrow keys events and transform it to new scroll + */ + const handleKeyDown = (event: React.KeyboardEvent) => { + event.preventDefault(); + let newScrollY = scrollY; + let newScrollX = scrollX; + let isX = true; + switch (event.key) { + case "Down": // IE/Edge specific value + case "ArrowDown": + newScrollY += rowHeight; + isX = false; + break; + case "Up": // IE/Edge specific value + case "ArrowUp": + newScrollY -= rowHeight; + isX = false; + break; + case "Left": + case "ArrowLeft": + newScrollX -= columnWidth; + break; + case "Right": // IE/Edge specific value + case "ArrowRight": + newScrollX += columnWidth; + break; + } + if (isX) { + if (newScrollX < 0) { + newScrollX = 0; + } else if (newScrollX > svgWidth) { + newScrollX = svgWidth; + } + setScrollX(newScrollX); + } else { + if (newScrollY < 0) { + newScrollY = 0; + } else if (newScrollY > ganttFullHeight - ganttHeight) { + newScrollY = ganttFullHeight - ganttHeight; + } + setScrollY(newScrollY); + } + setIgnoreScrollEvent(true); + }; + + /** + * Task select event + */ + const handleSelectedTask = (taskId: string) => { + const newSelectedTask = barTasks.find(t => t.id === taskId); + const oldSelectedTask = barTasks.find( + t => !!selectedTask && t.id === selectedTask.id + ); + if (onSelect) { + if (oldSelectedTask) { + onSelect(oldSelectedTask, false); + } + if (newSelectedTask) { + onSelect(newSelectedTask, true); + } + } + setSelectedTask(newSelectedTask); + }; + const handleExpanderClick = (task: Task) => { + if (onExpanderClick && task.hideChildren !== undefined) { + onExpanderClick({ ...task, hideChildren: !task.hideChildren }); + } + }; + const gridProps: GridProps = { + columnWidth, + svgWidth, + tasks: tasks, + rowHeight, + dates: dateSetup.dates, + todayColor, + rtl, + }; + const calendarProps: CalendarProps = { + dateSetup, + locale, + viewMode, + headerHeight, + columnWidth, + fontFamily, + fontSize, + rtl, + }; + const barProps: TaskGanttContentProps = { + tasks: barTasks, + dates: dateSetup.dates, + ganttEvent, + selectedTask, + rowHeight, + taskHeight, + columnWidth, + arrowColor, + timeStep, + fontFamily, + fontSize, + arrowIndent, + svgWidth, + rtl, + setGanttEvent, + setFailedTask, + setSelectedTask: handleSelectedTask, + onDateChange, + onProgressChange, + onDoubleClick, + onClick, + onDelete, + }; + + const tableProps: TaskListProps = { + rowHeight, + rowWidth: listCellWidth, + fontFamily, + fontSize, + tasks: barTasks, + locale, + headerHeight, + scrollY, + ganttHeight, + horizontalContainerClass: styles.horizontalContainer, + selectedTask, + taskListRef, + setSelectedTask: handleSelectedTask, + onExpanderClick: handleExpanderClick, + TaskListHeader, + TaskListTable, + }; + return ( +
+
+ {listCellWidth && } + + {ganttEvent.changedTask && ( + + )} + +
+ +
+ ); +}; diff --git a/application/frontend/src/lib/gantt/task-gantt-content.tsx b/application/frontend/src/lib/gantt/task-gantt-content.tsx new file mode 100644 index 0000000..33326df --- /dev/null +++ b/application/frontend/src/lib/gantt/task-gantt-content.tsx @@ -0,0 +1,302 @@ +import React, { useEffect, useState } from "react"; +import { EventOption } from "../../types/public-types"; +import { BarTask } from "../../types/bar-task"; +import { Arrow } from "../other/arrow"; +import { handleTaskBySVGMouseEvent } from "../../helpers/bar-helper"; +import { isKeyboardEvent } from "../../helpers/other-helper"; +import { TaskItem } from "../task-item/task-item"; +import { + BarMoveAction, + GanttContentMoveAction, + GanttEvent, +} from "../../types/gantt-task-actions"; + +export type TaskGanttContentProps = { + tasks: BarTask[]; + dates: Date[]; + ganttEvent: GanttEvent; + selectedTask: BarTask | undefined; + rowHeight: number; + columnWidth: number; + timeStep: number; + svg?: React.RefObject; + svgWidth: number; + taskHeight: number; + arrowColor: string; + arrowIndent: number; + fontSize: string; + fontFamily: string; + rtl: boolean; + setGanttEvent: (value: GanttEvent) => void; + setFailedTask: (value: BarTask | null) => void; + setSelectedTask: (taskId: string) => void; +} & EventOption; + +export const TaskGanttContent: React.FC = ({ + tasks, + dates, + ganttEvent, + selectedTask, + rowHeight, + columnWidth, + timeStep, + svg, + taskHeight, + arrowColor, + arrowIndent, + fontFamily, + fontSize, + rtl, + setGanttEvent, + setFailedTask, + setSelectedTask, + onDateChange, + onProgressChange, + onDoubleClick, + onClick, + onDelete, +}) => { + const point = svg?.current?.createSVGPoint(); + const [xStep, setXStep] = useState(0); + const [initEventX1Delta, setInitEventX1Delta] = useState(0); + const [isMoving, setIsMoving] = useState(false); + + // create xStep + useEffect(() => { + const dateDelta = + dates[1].getTime() - + dates[0].getTime() - + dates[1].getTimezoneOffset() * 60 * 1000 + + dates[0].getTimezoneOffset() * 60 * 1000; + const newXStep = (timeStep * columnWidth) / dateDelta; + setXStep(newXStep); + }, [columnWidth, dates, timeStep]); + + useEffect(() => { + const handleMouseMove = async (event: MouseEvent) => { + if (!ganttEvent.changedTask || !point || !svg?.current) return; + event.preventDefault(); + + point.x = event.clientX; + const cursor = point.matrixTransform( + svg?.current.getScreenCTM()?.inverse() + ); + + const { isChanged, changedTask } = handleTaskBySVGMouseEvent( + cursor.x, + ganttEvent.action as BarMoveAction, + ganttEvent.changedTask, + xStep, + timeStep, + initEventX1Delta, + rtl + ); + if (isChanged) { + setGanttEvent({ action: ganttEvent.action, changedTask }); + } + }; + + const handleMouseUp = async (event: MouseEvent) => { + const { action, originalSelectedTask, changedTask } = ganttEvent; + if (!changedTask || !point || !svg?.current || !originalSelectedTask) + return; + event.preventDefault(); + + point.x = event.clientX; + const cursor = point.matrixTransform( + svg?.current.getScreenCTM()?.inverse() + ); + const { changedTask: newChangedTask } = handleTaskBySVGMouseEvent( + cursor.x, + action as BarMoveAction, + changedTask, + xStep, + timeStep, + initEventX1Delta, + rtl + ); + + const isNotLikeOriginal = + originalSelectedTask.start !== newChangedTask.start || + originalSelectedTask.end !== newChangedTask.end || + originalSelectedTask.progress !== newChangedTask.progress; + + // remove listeners + svg.current.removeEventListener("mousemove", handleMouseMove); + svg.current.removeEventListener("mouseup", handleMouseUp); + setGanttEvent({ action: "" }); + setIsMoving(false); + + // custom operation start + let operationSuccess = true; + if ( + (action === "move" || action === "end" || action === "start") && + onDateChange && + isNotLikeOriginal + ) { + try { + const result = await onDateChange( + newChangedTask, + newChangedTask.barChildren + ); + if (result !== undefined) { + operationSuccess = result; + } + } catch (error) { + operationSuccess = false; + } + } else if (onProgressChange && isNotLikeOriginal) { + try { + const result = await onProgressChange( + newChangedTask, + newChangedTask.barChildren + ); + if (result !== undefined) { + operationSuccess = result; + } + } catch (error) { + operationSuccess = false; + } + } + + // If operation is failed - return old state + if (!operationSuccess) { + setFailedTask(originalSelectedTask); + } + }; + + if ( + !isMoving && + (ganttEvent.action === "move" || + ganttEvent.action === "end" || + ganttEvent.action === "start" || + ganttEvent.action === "progress") && + svg?.current + ) { + svg.current.addEventListener("mousemove", handleMouseMove); + svg.current.addEventListener("mouseup", handleMouseUp); + setIsMoving(true); + } + }, [ + ganttEvent, + xStep, + initEventX1Delta, + onProgressChange, + timeStep, + onDateChange, + svg, + isMoving, + point, + rtl, + setFailedTask, + setGanttEvent, + ]); + + /** + * Method is Start point of task change + */ + const handleBarEventStart = async ( + action: GanttContentMoveAction, + task: BarTask, + event?: React.MouseEvent | React.KeyboardEvent + ) => { + if (!event) { + if (action === "select") { + setSelectedTask(task.id); + } + } + // Keyboard events + else if (isKeyboardEvent(event)) { + if (action === "delete") { + if (onDelete) { + try { + const result = await onDelete(task); + if (result !== undefined && result) { + setGanttEvent({ action, changedTask: task }); + } + } catch (error) { + console.error("Error on Delete. " + error); + } + } + } + } + // Mouse Events + else if (action === "mouseenter") { + if (!ganttEvent.action) { + setGanttEvent({ + action, + changedTask: task, + originalSelectedTask: task, + }); + } + } else if (action === "mouseleave") { + if (ganttEvent.action === "mouseenter") { + setGanttEvent({ action: "" }); + } + } else if (action === "dblclick") { + !!onDoubleClick && onDoubleClick(task); + } else if (action === "click") { + !!onClick && onClick(task); + } + // Change task event start + else if (action === "move") { + if (!svg?.current || !point) return; + point.x = event.clientX; + const cursor = point.matrixTransform( + svg.current.getScreenCTM()?.inverse() + ); + setInitEventX1Delta(cursor.x - task.x1); + setGanttEvent({ + action, + changedTask: task, + originalSelectedTask: task, + }); + } else { + setGanttEvent({ + action, + changedTask: task, + originalSelectedTask: task, + }); + } + }; + + return ( + + + {tasks.map(task => { + return task.barChildren.map(child => { + return ( + + ); + }); + })} + + + {tasks.map(task => { + return ( + + ); + })} + + + ); +}; diff --git a/application/frontend/src/lib/gantt/task-gantt.tsx b/application/frontend/src/lib/gantt/task-gantt.tsx new file mode 100644 index 0000000..73a7668 --- /dev/null +++ b/application/frontend/src/lib/gantt/task-gantt.tsx @@ -0,0 +1,76 @@ +import React, { useRef, useEffect } from "react"; +import { GridProps, Grid } from "../grid/grid"; +import { CalendarProps, Calendar } from "../calendar/calendar"; +import { TaskGanttContentProps, TaskGanttContent } from "./task-gantt-content"; +import styles from "./gantt.module.css"; + +export type TaskGanttProps = { + gridProps: GridProps; + calendarProps: CalendarProps; + barProps: TaskGanttContentProps; + ganttHeight: number; + scrollY: number; + scrollX: number; +}; +export const TaskGantt: React.FC = ({ + gridProps, + calendarProps, + barProps, + ganttHeight, + scrollY, + scrollX, +}) => { + const ganttSVGRef = useRef(null); + const horizontalContainerRef = useRef(null); + const verticalGanttContainerRef = useRef(null); + const newBarProps = { ...barProps, svg: ganttSVGRef }; + + useEffect(() => { + if (horizontalContainerRef.current) { + horizontalContainerRef.current.scrollTop = scrollY; + } + }, [scrollY]); + + useEffect(() => { + if (verticalGanttContainerRef.current) { + verticalGanttContainerRef.current.scrollLeft = scrollX; + } + }, [scrollX]); + + return ( +
+ + + +
+ + + + +
+
+ ); +}; diff --git a/application/frontend/src/lib/grid/grid-body.tsx b/application/frontend/src/lib/grid/grid-body.tsx new file mode 100644 index 0000000..18e6f2b --- /dev/null +++ b/application/frontend/src/lib/grid/grid-body.tsx @@ -0,0 +1,127 @@ +import React, { ReactChild } from "react"; +import { Task } from "../../types/public-types"; +import { addToDate } from "../../helpers/date-helper"; +import styles from "./grid.module.css"; + +export type GridBodyProps = { + tasks: Task[]; + dates: Date[]; + svgWidth: number; + rowHeight: number; + columnWidth: number; + todayColor: string; + rtl: boolean; +}; +export const GridBody: React.FC = ({ + tasks, + dates, + rowHeight, + svgWidth, + columnWidth, + todayColor, + rtl, +}) => { + let y = 0; + const gridRows: ReactChild[] = []; + const rowLines: ReactChild[] = [ + , + ]; + for (const task of tasks) { + gridRows.push( + + ); + rowLines.push( + + ); + y += rowHeight; + } + + const now = new Date(); + let tickX = 0; + const ticks: ReactChild[] = []; + let today: ReactChild = ; + for (let i = 0; i < dates.length; i++) { + const date = dates[i]; + ticks.push( + + ); + if ( + (i + 1 !== dates.length && + date.getTime() < now.getTime() && + dates[i + 1].getTime() >= now.getTime()) || + // if current date is last + (i !== 0 && + i + 1 === dates.length && + date.getTime() < now.getTime() && + addToDate( + date, + date.getTime() - dates[i - 1].getTime(), + "millisecond" + ).getTime() >= now.getTime()) + ) { + today = ( + + ); + } + // rtl for today + if ( + rtl && + i + 1 !== dates.length && + date.getTime() >= now.getTime() && + dates[i + 1].getTime() < now.getTime() + ) { + today = ( + + ); + } + tickX += columnWidth; + } + return ( + + {gridRows} + {rowLines} + {ticks} + {today} + + ); +}; diff --git a/application/frontend/src/lib/grid/grid.module.css b/application/frontend/src/lib/grid/grid.module.css new file mode 100644 index 0000000..964303f --- /dev/null +++ b/application/frontend/src/lib/grid/grid.module.css @@ -0,0 +1,15 @@ +.gridRow { + fill: #fff; +} + +.gridRow:nth-child(even) { + fill: #f5f5f5; +} + +.gridRowLine { + stroke: #ebeff2; +} + +.gridTick { + stroke: #e6e4e4; +} diff --git a/application/frontend/src/lib/grid/grid.tsx b/application/frontend/src/lib/grid/grid.tsx new file mode 100644 index 0000000..488cfa3 --- /dev/null +++ b/application/frontend/src/lib/grid/grid.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { GridBody, GridBodyProps } from "./grid-body"; + +export type GridProps = GridBodyProps; +export const Grid: React.FC = props => { + return ( + + + + ); +}; diff --git a/application/frontend/src/lib/other/arrow.tsx b/application/frontend/src/lib/other/arrow.tsx new file mode 100644 index 0000000..52e8f28 --- /dev/null +++ b/application/frontend/src/lib/other/arrow.tsx @@ -0,0 +1,106 @@ +import React from "react"; +import { BarTask } from "../../types/bar-task"; + +type ArrowProps = { + taskFrom: BarTask; + taskTo: BarTask; + rowHeight: number; + taskHeight: number; + arrowIndent: number; + rtl: boolean; +}; +export const Arrow: React.FC = ({ + taskFrom, + taskTo, + rowHeight, + taskHeight, + arrowIndent, + rtl, +}) => { + let path: string; + let trianglePoints: string; + if (rtl) { + [path, trianglePoints] = drownPathAndTriangleRTL( + taskFrom, + taskTo, + rowHeight, + taskHeight, + arrowIndent + ); + } else { + [path, trianglePoints] = drownPathAndTriangle( + taskFrom, + taskTo, + rowHeight, + taskHeight, + arrowIndent + ); + } + + return ( + + + + + ); +}; + +const drownPathAndTriangle = ( + taskFrom: BarTask, + taskTo: BarTask, + rowHeight: number, + taskHeight: number, + arrowIndent: number +) => { + const indexCompare = taskFrom.index > taskTo.index ? -1 : 1; + const taskToEndPosition = taskTo.y + taskHeight / 2; + const taskFromEndPosition = taskFrom.x2 + arrowIndent * 2; + const taskFromHorizontalOffsetValue = + taskFromEndPosition < taskTo.x1 ? "" : `H ${taskTo.x1 - arrowIndent}`; + const taskToHorizontalOffsetValue = + taskFromEndPosition > taskTo.x1 + ? arrowIndent + : taskTo.x1 - taskFrom.x2 - arrowIndent; + + const path = `M ${taskFrom.x2} ${taskFrom.y + taskHeight / 2} + h ${arrowIndent} + v ${(indexCompare * rowHeight) / 2} + ${taskFromHorizontalOffsetValue} + V ${taskToEndPosition} + h ${taskToHorizontalOffsetValue}`; + + const trianglePoints = `${taskTo.x1},${taskToEndPosition} + ${taskTo.x1 - 5},${taskToEndPosition - 5} + ${taskTo.x1 - 5},${taskToEndPosition + 5}`; + return [path, trianglePoints]; +}; + +const drownPathAndTriangleRTL = ( + taskFrom: BarTask, + taskTo: BarTask, + rowHeight: number, + taskHeight: number, + arrowIndent: number +) => { + const indexCompare = taskFrom.index > taskTo.index ? -1 : 1; + const taskToEndPosition = taskTo.y + taskHeight / 2; + const taskFromEndPosition = taskFrom.x1 - arrowIndent * 2; + const taskFromHorizontalOffsetValue = + taskFromEndPosition > taskTo.x2 ? "" : `H ${taskTo.x2 + arrowIndent}`; + const taskToHorizontalOffsetValue = + taskFromEndPosition < taskTo.x2 + ? -arrowIndent + : taskTo.x2 - taskFrom.x1 + arrowIndent; + + const path = `M ${taskFrom.x1} ${taskFrom.y + taskHeight / 2} + h ${-arrowIndent} + v ${(indexCompare * rowHeight) / 2} + ${taskFromHorizontalOffsetValue} + V ${taskToEndPosition} + h ${taskToHorizontalOffsetValue}`; + + const trianglePoints = `${taskTo.x2},${taskToEndPosition} + ${taskTo.x2 + 5},${taskToEndPosition + 5} + ${taskTo.x2 + 5},${taskToEndPosition - 5}`; + return [path, trianglePoints]; +}; diff --git a/application/frontend/src/lib/other/horizontal-scroll.module.css b/application/frontend/src/lib/other/horizontal-scroll.module.css new file mode 100644 index 0000000..dcf787e --- /dev/null +++ b/application/frontend/src/lib/other/horizontal-scroll.module.css @@ -0,0 +1,33 @@ +.scrollWrapper { + overflow: auto; + max-width: 100%; + /*firefox*/ + scrollbar-width: thin; + /*iPad*/ + height: 1.2rem; +} +.scrollWrapper::-webkit-scrollbar { + width: 1.1rem; + height: 1.1rem; +} +.scrollWrapper::-webkit-scrollbar-corner { + background: transparent; +} +.scrollWrapper::-webkit-scrollbar-thumb { + border: 6px solid transparent; + background: rgba(0, 0, 0, 0.2); + background: var(--palette-black-alpha-20, rgba(0, 0, 0, 0.2)); + border-radius: 10px; + background-clip: padding-box; +} +.scrollWrapper::-webkit-scrollbar-thumb:hover { + border: 4px solid transparent; + background: rgba(0, 0, 0, 0.3); + background: var(--palette-black-alpha-30, rgba(0, 0, 0, 0.3)); + background-clip: padding-box; +} +@media only screen and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) { +} +.scroll { + height: 1px; +} diff --git a/application/frontend/src/lib/other/horizontal-scroll.tsx b/application/frontend/src/lib/other/horizontal-scroll.tsx new file mode 100644 index 0000000..5426460 --- /dev/null +++ b/application/frontend/src/lib/other/horizontal-scroll.tsx @@ -0,0 +1,34 @@ +import React, { SyntheticEvent, useRef, useEffect } from "react"; +import styles from "./horizontal-scroll.module.css"; + +export const HorizontalScroll: React.FC<{ + scroll: number; + svgWidth: number; + taskListWidth: number; + rtl: boolean; + onScroll: (event: SyntheticEvent) => void; +}> = ({ scroll, svgWidth, taskListWidth, rtl, onScroll }) => { + const scrollRef = useRef(null); + + useEffect(() => { + if (scrollRef.current) { + scrollRef.current.scrollLeft = scroll; + } + }, [scroll]); + + return ( +
+
+
+ ); +}; diff --git a/application/frontend/src/lib/other/tooltip.module.css b/application/frontend/src/lib/other/tooltip.module.css new file mode 100644 index 0000000..d5793ef --- /dev/null +++ b/application/frontend/src/lib/other/tooltip.module.css @@ -0,0 +1,30 @@ +.tooltipDefaultContainer { + background: #fff; + padding: 12px; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); +} + +.tooltipDefaultContainerParagraph { + font-size: 12px; + margin-bottom: 6px; + color: #666; +} + +.tooltipDetailsContainer { + position: absolute; + display: flex; + flex-shrink: 0; + pointer-events: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.tooltipDetailsContainerHidden { + visibility: hidden; + position: absolute; + display: flex; + pointer-events: none; +} diff --git a/application/frontend/src/lib/other/tooltip.tsx b/application/frontend/src/lib/other/tooltip.tsx new file mode 100644 index 0000000..7542a14 --- /dev/null +++ b/application/frontend/src/lib/other/tooltip.tsx @@ -0,0 +1,145 @@ +import React, { useRef, useEffect, useState } from "react"; +import { Task } from "../../types/public-types"; +import { BarTask } from "../../types/bar-task"; +import styles from "./tooltip.module.css"; + +export type TooltipProps = { + task: BarTask; + arrowIndent: number; + rtl: boolean; + svgContainerHeight: number; + svgContainerWidth: number; + svgWidth: number; + headerHeight: number; + taskListWidth: number; + scrollX: number; + scrollY: number; + rowHeight: number; + fontSize: string; + fontFamily: string; + TooltipContent: React.FC<{ + task: Task; + fontSize: string; + fontFamily: string; + }>; +}; +export const Tooltip: React.FC = ({ + task, + rowHeight, + rtl, + svgContainerHeight, + svgContainerWidth, + scrollX, + scrollY, + arrowIndent, + fontSize, + fontFamily, + headerHeight, + taskListWidth, + TooltipContent, +}) => { + const tooltipRef = useRef(null); + const [relatedY, setRelatedY] = useState(0); + const [relatedX, setRelatedX] = useState(0); + useEffect(() => { + if (tooltipRef.current) { + const tooltipHeight = tooltipRef.current.offsetHeight * 1.1; + const tooltipWidth = tooltipRef.current.offsetWidth * 1.1; + + let newRelatedY = task.index * rowHeight - scrollY + headerHeight; + let newRelatedX: number; + if (rtl) { + newRelatedX = task.x1 - arrowIndent * 1.5 - tooltipWidth - scrollX; + if (newRelatedX < 0) { + newRelatedX = task.x2 + arrowIndent * 1.5 - scrollX; + } + const tooltipLeftmostPoint = tooltipWidth + newRelatedX; + if (tooltipLeftmostPoint > svgContainerWidth) { + newRelatedX = svgContainerWidth - tooltipWidth; + newRelatedY += rowHeight; + } + } else { + newRelatedX = task.x2 + arrowIndent * 1.5 + taskListWidth - scrollX; + const tooltipLeftmostPoint = tooltipWidth + newRelatedX; + const fullChartWidth = taskListWidth + svgContainerWidth; + if (tooltipLeftmostPoint > fullChartWidth) { + newRelatedX = + task.x1 + + taskListWidth - + arrowIndent * 1.5 - + scrollX - + tooltipWidth; + } + if (newRelatedX < taskListWidth) { + newRelatedX = svgContainerWidth + taskListWidth - tooltipWidth; + newRelatedY += rowHeight; + } + } + + const tooltipLowerPoint = tooltipHeight + newRelatedY - scrollY; + if (tooltipLowerPoint > svgContainerHeight - scrollY) { + newRelatedY = svgContainerHeight - tooltipHeight; + } + setRelatedY(newRelatedY); + setRelatedX(newRelatedX); + } + }, [ + tooltipRef, + task, + arrowIndent, + scrollX, + scrollY, + headerHeight, + taskListWidth, + rowHeight, + svgContainerHeight, + svgContainerWidth, + rtl, + ]); + + return ( +
+ +
+ ); +}; + +export const StandardTooltipContent: React.FC<{ + task: Task; + fontSize: string; + fontFamily: string; +}> = ({ task, fontSize, fontFamily }) => { + const style = { + fontSize, + fontFamily, + }; + return ( +
+ {`${ + task.name + }: ${task.start.getDate()}-${ + task.start.getMonth() + 1 + }-${task.start.getFullYear()} - ${task.end.getDate()}-${ + task.end.getMonth() + 1 + }-${task.end.getFullYear()}`} + {task.end.getTime() - task.start.getTime() !== 0 && ( +

{`Duration: ${~~( + (task.end.getTime() - task.start.getTime()) / + (1000 * 60 * 60 * 24) + )} day(s)`}

+ )} + +

+ {!!task.progress && `Progress: ${task.progress} %`} +

+
+ ); +}; diff --git a/application/frontend/src/lib/other/vertical-scroll.module.css b/application/frontend/src/lib/other/vertical-scroll.module.css new file mode 100644 index 0000000..da55a2e --- /dev/null +++ b/application/frontend/src/lib/other/vertical-scroll.module.css @@ -0,0 +1,27 @@ +.scroll { + overflow: hidden auto; + width: 1rem; + flex-shrink: 0; + /*firefox*/ + scrollbar-width: thin; +} +.scroll::-webkit-scrollbar { + width: 1.1rem; + height: 1.1rem; +} +.scroll::-webkit-scrollbar-corner { + background: transparent; +} +.scroll::-webkit-scrollbar-thumb { + border: 6px solid transparent; + background: rgba(0, 0, 0, 0.2); + background: var(--palette-black-alpha-20, rgba(0, 0, 0, 0.2)); + border-radius: 10px; + background-clip: padding-box; +} +.scroll::-webkit-scrollbar-thumb:hover { + border: 4px solid transparent; + background: rgba(0, 0, 0, 0.3); + background: var(--palette-black-alpha-30, rgba(0, 0, 0, 0.3)); + background-clip: padding-box; +} diff --git a/application/frontend/src/lib/other/vertical-scroll.tsx b/application/frontend/src/lib/other/vertical-scroll.tsx new file mode 100644 index 0000000..d01d46e --- /dev/null +++ b/application/frontend/src/lib/other/vertical-scroll.tsx @@ -0,0 +1,41 @@ +import React, { SyntheticEvent, useRef, useEffect } from "react"; +import styles from "./vertical-scroll.module.css"; + +export const VerticalScroll: React.FC<{ + scroll: number; + ganttHeight: number; + ganttFullHeight: number; + headerHeight: number; + rtl: boolean; + onScroll: (event: SyntheticEvent) => void; +}> = ({ + scroll, + ganttHeight, + ganttFullHeight, + headerHeight, + rtl, + onScroll, +}) => { + const scrollRef = useRef(null); + + useEffect(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scroll; + } + }, [scroll]); + + return ( +
+
+
+ ); +}; diff --git a/application/frontend/src/lib/task-item/bar/bar-date-handle.tsx b/application/frontend/src/lib/task-item/bar/bar-date-handle.tsx new file mode 100644 index 0000000..3794239 --- /dev/null +++ b/application/frontend/src/lib/task-item/bar/bar-date-handle.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import styles from "./bar.module.css"; + +type BarDateHandleProps = { + x: number; + y: number; + width: number; + height: number; + barCornerRadius: number; + onMouseDown: (event: React.MouseEvent) => void; +}; +export const BarDateHandle: React.FC = ({ + x, + y, + width, + height, + barCornerRadius, + onMouseDown, +}) => { + return ( + + ); +}; diff --git a/application/frontend/src/lib/task-item/bar/bar-display.tsx b/application/frontend/src/lib/task-item/bar/bar-display.tsx new file mode 100644 index 0000000..174e1ed --- /dev/null +++ b/application/frontend/src/lib/task-item/bar/bar-display.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import style from "./bar.module.css"; + +type BarDisplayProps = { + x: number; + y: number; + width: number; + height: number; + isSelected: boolean; + /* progress start point */ + progressX: number; + progressWidth: number; + barCornerRadius: number; + styles: { + backgroundColor: string; + backgroundSelectedColor: string; + progressColor: string; + progressSelectedColor: string; + }; + onMouseDown: (event: React.MouseEvent) => void; +}; +export const BarDisplay: React.FC = ({ + x, + y, + width, + height, + isSelected, + progressX, + progressWidth, + barCornerRadius, + styles, + onMouseDown, +}) => { + const getProcessColor = () => { + return isSelected ? styles.progressSelectedColor : styles.progressColor; + }; + + const getBarColor = () => { + return isSelected ? styles.backgroundSelectedColor : styles.backgroundColor; + }; + + return ( + + + + + ); +}; diff --git a/application/frontend/src/lib/task-item/bar/bar-progress-handle.tsx b/application/frontend/src/lib/task-item/bar/bar-progress-handle.tsx new file mode 100644 index 0000000..75168b4 --- /dev/null +++ b/application/frontend/src/lib/task-item/bar/bar-progress-handle.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import styles from "./bar.module.css"; + +type BarProgressHandleProps = { + progressPoint: string; + onMouseDown: (event: React.MouseEvent) => void; +}; +export const BarProgressHandle: React.FC = ({ + progressPoint, + onMouseDown, +}) => { + return ( + + ); +}; diff --git a/application/frontend/src/lib/task-item/bar/bar-small.tsx b/application/frontend/src/lib/task-item/bar/bar-small.tsx new file mode 100644 index 0000000..56f4343 --- /dev/null +++ b/application/frontend/src/lib/task-item/bar/bar-small.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { getProgressPoint } from "../../../helpers/bar-helper"; +import { BarDisplay } from "./bar-display"; +import { BarProgressHandle } from "./bar-progress-handle"; +import { TaskItemProps } from "../task-item"; +import styles from "./bar.module.css"; + +export const BarSmall: React.FC = ({ + task, + isProgressChangeable, + isDateChangeable, + onEventStart, + isSelected, +}) => { + const progressPoint = getProgressPoint( + task.progressWidth + task.x1, + task.y, + task.height + ); + return ( + + { + isDateChangeable && onEventStart("move", task, e); + }} + /> + + {isProgressChangeable && ( + { + onEventStart("progress", task, e); + }} + /> + )} + + + ); +}; diff --git a/application/frontend/src/lib/task-item/bar/bar.module.css b/application/frontend/src/lib/task-item/bar/bar.module.css new file mode 100644 index 0000000..7ff4926 --- /dev/null +++ b/application/frontend/src/lib/task-item/bar/bar.module.css @@ -0,0 +1,21 @@ +.barWrapper { + cursor: pointer; + outline: none; +} + +.barWrapper:hover .barHandle { + visibility: visible; + opacity: 1; +} + +.barHandle { + fill: #ddd; + cursor: ew-resize; + opacity: 0; + visibility: hidden; +} + +.barBackground { + user-select: none; + stroke-width: 0; +} diff --git a/application/frontend/src/lib/task-item/bar/bar.tsx b/application/frontend/src/lib/task-item/bar/bar.tsx new file mode 100644 index 0000000..7e6ce5b --- /dev/null +++ b/application/frontend/src/lib/task-item/bar/bar.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import { getProgressPoint } from "../../../helpers/bar-helper"; +import { BarDisplay } from "./bar-display"; +import { BarDateHandle } from "./bar-date-handle"; +import { BarProgressHandle } from "./bar-progress-handle"; +import { TaskItemProps } from "../task-item"; +import styles from "./bar.module.css"; + +export const Bar: React.FC = ({ + task, + isProgressChangeable, + isDateChangeable, + rtl, + onEventStart, + isSelected, +}) => { + const progressPoint = getProgressPoint( + +!rtl * task.progressWidth + task.progressX, + task.y, + task.height + ); + const handleHeight = task.height - 2; + return ( + + { + isDateChangeable && onEventStart("move", task, e); + }} + /> + + {isDateChangeable && ( + + {/* left */} + { + onEventStart("start", task, e); + }} + /> + {/* right */} + { + onEventStart("end", task, e); + }} + /> + + )} + {isProgressChangeable && ( + { + onEventStart("progress", task, e); + }} + /> + )} + + + ); +}; diff --git a/application/frontend/src/lib/task-item/milestone/milestone.module.css b/application/frontend/src/lib/task-item/milestone/milestone.module.css new file mode 100644 index 0000000..f8766ba --- /dev/null +++ b/application/frontend/src/lib/task-item/milestone/milestone.module.css @@ -0,0 +1,8 @@ +.milestoneWrapper { + cursor: pointer; + outline: none; +} + +.milestoneBackground { + user-select: none; +} diff --git a/application/frontend/src/lib/task-item/milestone/milestone.tsx b/application/frontend/src/lib/task-item/milestone/milestone.tsx new file mode 100644 index 0000000..a8e3922 --- /dev/null +++ b/application/frontend/src/lib/task-item/milestone/milestone.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { TaskItemProps } from "../task-item"; +import styles from "./milestone.module.css"; + +export const Milestone: React.FC = ({ + task, + isDateChangeable, + onEventStart, + isSelected, +}) => { + const transform = `rotate(45 ${task.x1 + task.height * 0.356} + ${task.y + task.height * 0.85})`; + const getBarColor = () => { + return isSelected + ? task.styles.backgroundSelectedColor + : task.styles.backgroundColor; + }; + + return ( + + { + isDateChangeable && onEventStart("move", task, e); + }} + /> + + ); +}; diff --git a/application/frontend/src/lib/task-item/project/project.module.css b/application/frontend/src/lib/task-item/project/project.module.css new file mode 100644 index 0000000..4fa67c2 --- /dev/null +++ b/application/frontend/src/lib/task-item/project/project.module.css @@ -0,0 +1,13 @@ +.projectWrapper { + cursor: pointer; + outline: none; +} + +.projectBackground { + user-select: none; + opacity: 0.6; +} + +.projectTop { + user-select: none; +} diff --git a/application/frontend/src/lib/task-item/project/project.tsx b/application/frontend/src/lib/task-item/project/project.tsx new file mode 100644 index 0000000..5a47ba9 --- /dev/null +++ b/application/frontend/src/lib/task-item/project/project.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { TaskItemProps } from "../task-item"; +import styles from "./project.module.css"; + +export const Project: React.FC = ({ task, isSelected }) => { + const barColor = isSelected + ? task.styles.backgroundSelectedColor + : task.styles.backgroundColor; + const processColor = isSelected + ? task.styles.progressSelectedColor + : task.styles.progressColor; + const projectWith = task.x2 - task.x1; + + const projectLeftTriangle = [ + task.x1, + task.y + task.height / 2 - 1, + task.x1, + task.y + task.height, + task.x1 + 15, + task.y + task.height / 2 - 1, + ].join(","); + const projectRightTriangle = [ + task.x2, + task.y + task.height / 2 - 1, + task.x2, + task.y + task.height, + task.x2 - 15, + task.y + task.height / 2 - 1, + ].join(","); + + return ( + + + + + + + + ); +}; diff --git a/application/frontend/src/lib/task-item/task-item.tsx b/application/frontend/src/lib/task-item/task-item.tsx new file mode 100644 index 0000000..cf06284 --- /dev/null +++ b/application/frontend/src/lib/task-item/task-item.tsx @@ -0,0 +1,125 @@ +import React, { useEffect, useRef, useState } from "react"; +import { BarTask } from "../../types/bar-task"; +import { GanttContentMoveAction } from "../../types/gantt-task-actions"; +import { Bar } from "./bar/bar"; +import { BarSmall } from "./bar/bar-small"; +import { Milestone } from "./milestone/milestone"; +import { Project } from "./project/project"; +import style from "./task-list.module.css"; + +export type TaskItemProps = { + task: BarTask; + arrowIndent: number; + taskHeight: number; + isProgressChangeable: boolean; + isDateChangeable: boolean; + isDelete: boolean; + isSelected: boolean; + rtl: boolean; + onEventStart: ( + action: GanttContentMoveAction, + selectedTask: BarTask, + event?: React.MouseEvent | React.KeyboardEvent + ) => any; +}; + +export const TaskItem: React.FC = props => { + const { + task, + arrowIndent, + isDelete, + taskHeight, + isSelected, + rtl, + onEventStart, + } = { + ...props, + }; + const textRef = useRef(null); + const [taskItem, setTaskItem] = useState(
); + const [isTextInside, setIsTextInside] = useState(true); + + useEffect(() => { + switch (task.typeInternal) { + case "milestone": + setTaskItem(); + break; + case "project": + setTaskItem(); + break; + case "smalltask": + setTaskItem(); + break; + default: + setTaskItem(); + break; + } + }, [task, isSelected]); + + useEffect(() => { + if (textRef.current) { + setIsTextInside(textRef.current.getBBox().width < task.x2 - task.x1); + } + }, [textRef, task]); + + const getX = () => { + const width = task.x2 - task.x1; + const hasChild = task.barChildren.length > 0; + if (isTextInside) { + return task.x1 + width * 0.5; + } + if (rtl && textRef.current) { + return ( + task.x1 - + textRef.current.getBBox().width - + arrowIndent * +hasChild - + arrowIndent * 0.2 + ); + } else { + return task.x1 + width + arrowIndent * +hasChild + arrowIndent * 0.2; + } + }; + + return ( + { + switch (e.key) { + case "Delete": { + if (isDelete) onEventStart("delete", task, e); + break; + } + } + e.stopPropagation(); + }} + onMouseEnter={e => { + onEventStart("mouseenter", task, e); + }} + onMouseLeave={e => { + onEventStart("mouseleave", task, e); + }} + onDoubleClick={e => { + onEventStart("dblclick", task, e); + }} + onClick={e => { + onEventStart("click", task, e); + }} + onFocus={() => { + onEventStart("select", task); + }} + > + {taskItem} + + {task.name} + + + ); +}; diff --git a/application/frontend/src/lib/task-item/task-list.module.css b/application/frontend/src/lib/task-item/task-list.module.css new file mode 100644 index 0000000..2eec420 --- /dev/null +++ b/application/frontend/src/lib/task-item/task-list.module.css @@ -0,0 +1,23 @@ +.barLabel { + fill: #fff; + text-anchor: middle; + font-weight: lighter; + dominant-baseline: central; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; +} + +.barLabelOutside { + fill: #555; + text-anchor: start; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; +} diff --git a/application/frontend/src/lib/task-list/task-list-header.module.css b/application/frontend/src/lib/task-list/task-list-header.module.css new file mode 100644 index 0000000..c250354 --- /dev/null +++ b/application/frontend/src/lib/task-list/task-list-header.module.css @@ -0,0 +1,23 @@ +.ganttTable { + display: table; + border-bottom: #e6e4e4 1px solid; + border-top: #e6e4e4 1px solid; + border-left: #e6e4e4 1px solid; +} + +.ganttTable_Header { + display: table-row; + list-style: none; +} + +.ganttTable_HeaderSeparator { + border-right: 1px solid rgb(196, 196, 196); + opacity: 1; + margin-left: -2px; +} + +.ganttTable_HeaderItem { + display: table-cell; + vertical-align: -webkit-baseline-middle; + vertical-align: middle; +} diff --git a/application/frontend/src/lib/task-list/task-list-header.tsx b/application/frontend/src/lib/task-list/task-list-header.tsx new file mode 100644 index 0000000..4e8cdb6 --- /dev/null +++ b/application/frontend/src/lib/task-list/task-list-header.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import styles from "./task-list-header.module.css"; + +export const TaskListHeaderDefault: React.FC<{ + headerHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; +}> = ({ headerHeight, fontFamily, fontSize, rowWidth }) => { + return ( +
+
+
+  Name +
+
+
+  From +
+
+
+  To +
+
+
+ ); +}; diff --git a/application/frontend/src/lib/task-list/task-list-table.module.css b/application/frontend/src/lib/task-list/task-list-table.module.css new file mode 100644 index 0000000..7f57268 --- /dev/null +++ b/application/frontend/src/lib/task-list/task-list-table.module.css @@ -0,0 +1,38 @@ +.taskListWrapper { + display: table; + border-bottom: #e6e4e4 1px solid; + border-left: #e6e4e4 1px solid; +} + +.taskListTableRow { + display: table-row; + text-overflow: ellipsis; +} + +.taskListTableRow:nth-of-type(even) { + background-color: #f5f5f5; +} + +.taskListCell { + display: table-cell; + vertical-align: middle; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.taskListNameWrapper { + display: flex; +} + +.taskListExpander { + color: rgb(86 86 86); + font-size: 0.6rem; + padding: 0.15rem 0.2rem 0rem 0.2rem; + user-select: none; + cursor: pointer; +} +.taskListEmptyExpander { + font-size: 0.6rem; + padding-left: 1rem; + user-select: none; +} diff --git a/application/frontend/src/lib/task-list/task-list-table.tsx b/application/frontend/src/lib/task-list/task-list-table.tsx new file mode 100644 index 0000000..b165f60 --- /dev/null +++ b/application/frontend/src/lib/task-list/task-list-table.tsx @@ -0,0 +1,115 @@ +import React, { useMemo } from "react"; +import styles from "./task-list-table.module.css"; +import { Task } from "../../types/public-types"; + +const localeDateStringCache = {}; +const toLocaleDateStringFactory = + (locale: string) => + (date: Date, dateTimeOptions: Intl.DateTimeFormatOptions) => { + const key = date.toString(); + let lds = localeDateStringCache[key]; + if (!lds) { + lds = date.toLocaleDateString(locale, dateTimeOptions); + localeDateStringCache[key] = lds; + } + return lds; + }; +const dateTimeOptions: Intl.DateTimeFormatOptions = { + weekday: "short", + year: "numeric", + month: "long", + day: "numeric", +}; + +export const TaskListTableDefault: React.FC<{ + rowHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + locale: string; + tasks: Task[]; + selectedTaskId: string; + setSelectedTask: (taskId: string) => void; + onExpanderClick: (task: Task) => void; +}> = ({ + rowHeight, + rowWidth, + tasks, + fontFamily, + fontSize, + locale, + onExpanderClick, +}) => { + const toLocaleDateString = useMemo( + () => toLocaleDateStringFactory(locale), + [locale] + ); + + return ( +
+ {tasks.map(t => { + let expanderSymbol = ""; + if (t.hideChildren === false) { + expanderSymbol = "▼"; + } else if (t.hideChildren === true) { + expanderSymbol = "▶"; + } + + return ( +
+
+
+
onExpanderClick(t)} + > + {expanderSymbol} +
+
{t.name}
+
+
+
+  {toLocaleDateString(t.start, dateTimeOptions)} +
+
+  {toLocaleDateString(t.end, dateTimeOptions)} +
+
+ ); + })} +
+ ); +}; diff --git a/application/frontend/src/lib/task-list/task-list.tsx b/application/frontend/src/lib/task-list/task-list.tsx new file mode 100644 index 0000000..bbfed43 --- /dev/null +++ b/application/frontend/src/lib/task-list/task-list.tsx @@ -0,0 +1,95 @@ +import React, { useEffect, useRef } from "react"; +import { BarTask } from "../../types/bar-task"; +import { Task } from "../../types/public-types"; + +export type TaskListProps = { + headerHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + rowHeight: number; + ganttHeight: number; + scrollY: number; + locale: string; + tasks: Task[]; + taskListRef: React.RefObject; + horizontalContainerClass?: string; + selectedTask: BarTask | undefined; + setSelectedTask: (task: string) => void; + onExpanderClick: (task: Task) => void; + TaskListHeader: React.FC<{ + headerHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + }>; + TaskListTable: React.FC<{ + rowHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + locale: string; + tasks: Task[]; + selectedTaskId: string; + setSelectedTask: (taskId: string) => void; + onExpanderClick: (task: Task) => void; + }>; +}; + +export const TaskList: React.FC = ({ + headerHeight, + fontFamily, + fontSize, + rowWidth, + rowHeight, + scrollY, + tasks, + selectedTask, + setSelectedTask, + onExpanderClick, + locale, + ganttHeight, + taskListRef, + horizontalContainerClass, + TaskListHeader, + TaskListTable, +}) => { + const horizontalContainerRef = useRef(null); + useEffect(() => { + if (horizontalContainerRef.current) { + horizontalContainerRef.current.scrollTop = scrollY; + } + }, [scrollY]); + + const headerProps = { + headerHeight, + fontFamily, + fontSize, + rowWidth, + }; + const selectedTaskId = selectedTask ? selectedTask.id : ""; + const tableProps = { + rowHeight, + rowWidth, + fontFamily, + fontSize, + tasks, + locale, + selectedTaskId: selectedTaskId, + setSelectedTask, + onExpanderClick, + }; + + return ( +
+ +
+ +
+
+ ); +}; From df3a3b90b70450e71382c7c48944c85130989c8d Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 21:39:52 +0300 Subject: [PATCH 39/42] feat: init gantt lib --- .../frontend/src/helpers/bar-helper.ts | 589 ++++++++++++++++++ .../frontend/src/helpers/date-helper.ts | 241 +++++++ .../frontend/src/helpers/other-helper.ts | 61 ++ application/frontend/src/lib/gantt/gantt.tsx | 309 +++++---- .../frontend/src/pages/report/Report.tsx | 101 ++- application/frontend/src/types/bar-task.ts | 23 + application/frontend/src/types/date-setup.ts | 6 + .../frontend/src/types/gantt-task-actions.ts | 18 + .../frontend/src/types/public-types.ts | 145 +++++ 9 files changed, 1321 insertions(+), 172 deletions(-) create mode 100644 application/frontend/src/helpers/bar-helper.ts create mode 100644 application/frontend/src/helpers/date-helper.ts create mode 100644 application/frontend/src/helpers/other-helper.ts create mode 100644 application/frontend/src/types/bar-task.ts create mode 100644 application/frontend/src/types/date-setup.ts create mode 100644 application/frontend/src/types/gantt-task-actions.ts create mode 100644 application/frontend/src/types/public-types.ts diff --git a/application/frontend/src/helpers/bar-helper.ts b/application/frontend/src/helpers/bar-helper.ts new file mode 100644 index 0000000..ba5f987 --- /dev/null +++ b/application/frontend/src/helpers/bar-helper.ts @@ -0,0 +1,589 @@ +import { Task } from "../types/public-types"; +import { BarTask, TaskTypeInternal } from "../types/bar-task"; +import { BarMoveAction } from "../types/gantt-task-actions"; + +export const convertToBarTasks = ( + tasks: Task[], + dates: Date[], + columnWidth: number, + rowHeight: number, + taskHeight: number, + barCornerRadius: number, + handleWidth: number, + rtl: boolean, + barProgressColor: string, + barProgressSelectedColor: string, + barBackgroundColor: string, + barBackgroundSelectedColor: string, + projectProgressColor: string, + projectProgressSelectedColor: string, + projectBackgroundColor: string, + projectBackgroundSelectedColor: string, + milestoneBackgroundColor: string, + milestoneBackgroundSelectedColor: string +) => { + let barTasks = tasks.map((t, i) => { + return convertToBarTask( + t, + i, + dates, + columnWidth, + rowHeight, + taskHeight, + barCornerRadius, + handleWidth, + rtl, + barProgressColor, + barProgressSelectedColor, + barBackgroundColor, + barBackgroundSelectedColor, + projectProgressColor, + projectProgressSelectedColor, + projectBackgroundColor, + projectBackgroundSelectedColor, + milestoneBackgroundColor, + milestoneBackgroundSelectedColor + ); + }); + + // set dependencies + barTasks = barTasks.map(task => { + const dependencies = task.dependencies || []; + for (let j = 0; j < dependencies.length; j++) { + const dependence = barTasks.findIndex( + value => value.id === dependencies[j] + ); + if (dependence !== -1) barTasks[dependence].barChildren.push(task); + } + return task; + }); + + return barTasks; +}; + +const convertToBarTask = ( + task: Task, + index: number, + dates: Date[], + columnWidth: number, + rowHeight: number, + taskHeight: number, + barCornerRadius: number, + handleWidth: number, + rtl: boolean, + barProgressColor: string, + barProgressSelectedColor: string, + barBackgroundColor: string, + barBackgroundSelectedColor: string, + projectProgressColor: string, + projectProgressSelectedColor: string, + projectBackgroundColor: string, + projectBackgroundSelectedColor: string, + milestoneBackgroundColor: string, + milestoneBackgroundSelectedColor: string +): BarTask => { + let barTask: BarTask; + switch (task.type) { + case "milestone": + barTask = convertToMilestone( + task, + index, + dates, + columnWidth, + rowHeight, + taskHeight, + barCornerRadius, + handleWidth, + milestoneBackgroundColor, + milestoneBackgroundSelectedColor + ); + break; + case "project": + barTask = convertToBar( + task, + index, + dates, + columnWidth, + rowHeight, + taskHeight, + barCornerRadius, + handleWidth, + rtl, + projectProgressColor, + projectProgressSelectedColor, + projectBackgroundColor, + projectBackgroundSelectedColor + ); + break; + default: + barTask = convertToBar( + task, + index, + dates, + columnWidth, + rowHeight, + taskHeight, + barCornerRadius, + handleWidth, + rtl, + barProgressColor, + barProgressSelectedColor, + barBackgroundColor, + barBackgroundSelectedColor + ); + break; + } + return barTask; +}; + +const convertToBar = ( + task: Task, + index: number, + dates: Date[], + columnWidth: number, + rowHeight: number, + taskHeight: number, + barCornerRadius: number, + handleWidth: number, + rtl: boolean, + barProgressColor: string, + barProgressSelectedColor: string, + barBackgroundColor: string, + barBackgroundSelectedColor: string +): BarTask => { + let x1: number; + let x2: number; + if (rtl) { + x2 = taskXCoordinateRTL(task.start, dates, columnWidth); + x1 = taskXCoordinateRTL(task.end, dates, columnWidth); + } else { + x1 = taskXCoordinate(task.start, dates, columnWidth); + x2 = taskXCoordinate(task.end, dates, columnWidth); + } + let typeInternal: TaskTypeInternal = task.type; + if (typeInternal === "task" && x2 - x1 < handleWidth * 2) { + typeInternal = "smalltask"; + x2 = x1 + handleWidth * 2; + } + + const [progressWidth, progressX] = progressWithByParams( + x1, + x2, + task.progress, + rtl + ); + const y = taskYCoordinate(index, rowHeight, taskHeight); + const hideChildren = task.type === "project" ? task.hideChildren : undefined; + + const styles = { + backgroundColor: barBackgroundColor, + backgroundSelectedColor: barBackgroundSelectedColor, + progressColor: barProgressColor, + progressSelectedColor: barProgressSelectedColor, + ...task.styles, + }; + return { + ...task, + typeInternal, + x1, + x2, + y, + index, + progressX, + progressWidth, + barCornerRadius, + handleWidth, + hideChildren, + height: taskHeight, + barChildren: [], + styles, + }; +}; + +const convertToMilestone = ( + task: Task, + index: number, + dates: Date[], + columnWidth: number, + rowHeight: number, + taskHeight: number, + barCornerRadius: number, + handleWidth: number, + milestoneBackgroundColor: string, + milestoneBackgroundSelectedColor: string +): BarTask => { + const x = taskXCoordinate(task.start, dates, columnWidth); + const y = taskYCoordinate(index, rowHeight, taskHeight); + + const x1 = x - taskHeight * 0.5; + const x2 = x + taskHeight * 0.5; + + const rotatedHeight = taskHeight / 1.414; + const styles = { + backgroundColor: milestoneBackgroundColor, + backgroundSelectedColor: milestoneBackgroundSelectedColor, + progressColor: "", + progressSelectedColor: "", + ...task.styles, + }; + return { + ...task, + end: task.start, + x1, + x2, + y, + index, + progressX: 0, + progressWidth: 0, + barCornerRadius, + handleWidth, + typeInternal: task.type, + progress: 0, + height: rotatedHeight, + hideChildren: undefined, + barChildren: [], + styles, + }; +}; + +const taskXCoordinate = (xDate: Date, dates: Date[], columnWidth: number) => { + const index = dates.findIndex(d => d.getTime() >= xDate.getTime()) - 1; + + const remainderMillis = xDate.getTime() - dates[index].getTime(); + const percentOfInterval = + remainderMillis / (dates[index + 1].getTime() - dates[index].getTime()); + const x = index * columnWidth + percentOfInterval * columnWidth; + return x; +}; +const taskXCoordinateRTL = ( + xDate: Date, + dates: Date[], + columnWidth: number +) => { + let x = taskXCoordinate(xDate, dates, columnWidth); + x += columnWidth; + return x; +}; +const taskYCoordinate = ( + index: number, + rowHeight: number, + taskHeight: number +) => { + const y = index * rowHeight + (rowHeight - taskHeight) / 2; + return y; +}; + +export const progressWithByParams = ( + taskX1: number, + taskX2: number, + progress: number, + rtl: boolean +) => { + const progressWidth = (taskX2 - taskX1) * progress * 0.01; + let progressX: number; + if (rtl) { + progressX = taskX2 - progressWidth; + } else { + progressX = taskX1; + } + return [progressWidth, progressX]; +}; + +export const progressByProgressWidth = ( + progressWidth: number, + barTask: BarTask +) => { + const barWidth = barTask.x2 - barTask.x1; + const progressPercent = Math.round((progressWidth * 100) / barWidth); + if (progressPercent >= 100) return 100; + else if (progressPercent <= 0) return 0; + else return progressPercent; +}; + +const progressByX = (x: number, task: BarTask) => { + if (x >= task.x2) return 100; + else if (x <= task.x1) return 0; + else { + const barWidth = task.x2 - task.x1; + const progressPercent = Math.round(((x - task.x1) * 100) / barWidth); + return progressPercent; + } +}; +const progressByXRTL = (x: number, task: BarTask) => { + if (x >= task.x2) return 0; + else if (x <= task.x1) return 100; + else { + const barWidth = task.x2 - task.x1; + const progressPercent = Math.round(((task.x2 - x) * 100) / barWidth); + return progressPercent; + } +}; + +export const getProgressPoint = ( + progressX: number, + taskY: number, + taskHeight: number +) => { + const point = [ + progressX - 5, + taskY + taskHeight, + progressX + 5, + taskY + taskHeight, + progressX, + taskY + taskHeight - 8.66, + ]; + return point.join(","); +}; + +const startByX = (x: number, xStep: number, task: BarTask) => { + if (x >= task.x2 - task.handleWidth * 2) { + x = task.x2 - task.handleWidth * 2; + } + const steps = Math.round((x - task.x1) / xStep); + const additionalXValue = steps * xStep; + const newX = task.x1 + additionalXValue; + return newX; +}; + +const endByX = (x: number, xStep: number, task: BarTask) => { + if (x <= task.x1 + task.handleWidth * 2) { + x = task.x1 + task.handleWidth * 2; + } + const steps = Math.round((x - task.x2) / xStep); + const additionalXValue = steps * xStep; + const newX = task.x2 + additionalXValue; + return newX; +}; + +const moveByX = (x: number, xStep: number, task: BarTask) => { + const steps = Math.round((x - task.x1) / xStep); + const additionalXValue = steps * xStep; + const newX1 = task.x1 + additionalXValue; + const newX2 = newX1 + task.x2 - task.x1; + return [newX1, newX2]; +}; + +const dateByX = ( + x: number, + taskX: number, + taskDate: Date, + xStep: number, + timeStep: number +) => { + let newDate = new Date(((x - taskX) / xStep) * timeStep + taskDate.getTime()); + newDate = new Date( + newDate.getTime() + + (newDate.getTimezoneOffset() - taskDate.getTimezoneOffset()) * 60000 + ); + return newDate; +}; + +/** + * Method handles event in real time(mousemove) and on finish(mouseup) + */ +export const handleTaskBySVGMouseEvent = ( + svgX: number, + action: BarMoveAction, + selectedTask: BarTask, + xStep: number, + timeStep: number, + initEventX1Delta: number, + rtl: boolean +): { isChanged: boolean; changedTask: BarTask } => { + let result: { isChanged: boolean; changedTask: BarTask }; + switch (selectedTask.type) { + case "milestone": + result = handleTaskBySVGMouseEventForMilestone( + svgX, + action, + selectedTask, + xStep, + timeStep, + initEventX1Delta + ); + break; + default: + result = handleTaskBySVGMouseEventForBar( + svgX, + action, + selectedTask, + xStep, + timeStep, + initEventX1Delta, + rtl + ); + break; + } + return result; +}; + +const handleTaskBySVGMouseEventForBar = ( + svgX: number, + action: BarMoveAction, + selectedTask: BarTask, + xStep: number, + timeStep: number, + initEventX1Delta: number, + rtl: boolean +): { isChanged: boolean; changedTask: BarTask } => { + const changedTask: BarTask = { ...selectedTask }; + let isChanged = false; + switch (action) { + case "progress": + if (rtl) { + changedTask.progress = progressByXRTL(svgX, selectedTask); + } else { + changedTask.progress = progressByX(svgX, selectedTask); + } + isChanged = changedTask.progress !== selectedTask.progress; + if (isChanged) { + const [progressWidth, progressX] = progressWithByParams( + changedTask.x1, + changedTask.x2, + changedTask.progress, + rtl + ); + changedTask.progressWidth = progressWidth; + changedTask.progressX = progressX; + } + break; + case "start": { + const newX1 = startByX(svgX, xStep, selectedTask); + changedTask.x1 = newX1; + isChanged = changedTask.x1 !== selectedTask.x1; + if (isChanged) { + if (rtl) { + changedTask.end = dateByX( + newX1, + selectedTask.x1, + selectedTask.end, + xStep, + timeStep + ); + } else { + changedTask.start = dateByX( + newX1, + selectedTask.x1, + selectedTask.start, + xStep, + timeStep + ); + } + const [progressWidth, progressX] = progressWithByParams( + changedTask.x1, + changedTask.x2, + changedTask.progress, + rtl + ); + changedTask.progressWidth = progressWidth; + changedTask.progressX = progressX; + } + break; + } + case "end": { + const newX2 = endByX(svgX, xStep, selectedTask); + changedTask.x2 = newX2; + isChanged = changedTask.x2 !== selectedTask.x2; + if (isChanged) { + if (rtl) { + changedTask.start = dateByX( + newX2, + selectedTask.x2, + selectedTask.start, + xStep, + timeStep + ); + } else { + changedTask.end = dateByX( + newX2, + selectedTask.x2, + selectedTask.end, + xStep, + timeStep + ); + } + const [progressWidth, progressX] = progressWithByParams( + changedTask.x1, + changedTask.x2, + changedTask.progress, + rtl + ); + changedTask.progressWidth = progressWidth; + changedTask.progressX = progressX; + } + break; + } + case "move": { + const [newMoveX1, newMoveX2] = moveByX( + svgX - initEventX1Delta, + xStep, + selectedTask + ); + isChanged = newMoveX1 !== selectedTask.x1; + if (isChanged) { + changedTask.start = dateByX( + newMoveX1, + selectedTask.x1, + selectedTask.start, + xStep, + timeStep + ); + changedTask.end = dateByX( + newMoveX2, + selectedTask.x2, + selectedTask.end, + xStep, + timeStep + ); + changedTask.x1 = newMoveX1; + changedTask.x2 = newMoveX2; + const [progressWidth, progressX] = progressWithByParams( + changedTask.x1, + changedTask.x2, + changedTask.progress, + rtl + ); + changedTask.progressWidth = progressWidth; + changedTask.progressX = progressX; + } + break; + } + } + return { isChanged, changedTask }; +}; + +const handleTaskBySVGMouseEventForMilestone = ( + svgX: number, + action: BarMoveAction, + selectedTask: BarTask, + xStep: number, + timeStep: number, + initEventX1Delta: number +): { isChanged: boolean; changedTask: BarTask } => { + const changedTask: BarTask = { ...selectedTask }; + let isChanged = false; + switch (action) { + case "move": { + const [newMoveX1, newMoveX2] = moveByX( + svgX - initEventX1Delta, + xStep, + selectedTask + ); + isChanged = newMoveX1 !== selectedTask.x1; + if (isChanged) { + changedTask.start = dateByX( + newMoveX1, + selectedTask.x1, + selectedTask.start, + xStep, + timeStep + ); + changedTask.end = changedTask.start; + changedTask.x1 = newMoveX1; + changedTask.x2 = newMoveX2; + } + break; + } + } + return { isChanged, changedTask }; +}; diff --git a/application/frontend/src/helpers/date-helper.ts b/application/frontend/src/helpers/date-helper.ts new file mode 100644 index 0000000..1b2a0f5 --- /dev/null +++ b/application/frontend/src/helpers/date-helper.ts @@ -0,0 +1,241 @@ +import { Task, ViewMode } from "../types/public-types"; +import DateTimeFormatOptions = Intl.DateTimeFormatOptions; +import DateTimeFormat = Intl.DateTimeFormat; + +type DateHelperScales = + | "year" + | "month" + | "day" + | "hour" + | "minute" + | "second" + | "millisecond"; + +const intlDTCache = {}; +export const getCachedDateTimeFormat = ( + locString: string | string[], + opts: DateTimeFormatOptions = {} +): DateTimeFormat => { + const key = JSON.stringify([locString, opts]); + let dtf = intlDTCache[key]; + if (!dtf) { + dtf = new Intl.DateTimeFormat(locString, opts); + intlDTCache[key] = dtf; + } + return dtf; +}; + +export const addToDate = ( + date: Date, + quantity: number, + scale: DateHelperScales +) => { + const newDate = new Date( + date.getFullYear() + (scale === "year" ? quantity : 0), + date.getMonth() + (scale === "month" ? quantity : 0), + date.getDate() + (scale === "day" ? quantity : 0), + date.getHours() + (scale === "hour" ? quantity : 0), + date.getMinutes() + (scale === "minute" ? quantity : 0), + date.getSeconds() + (scale === "second" ? quantity : 0), + date.getMilliseconds() + (scale === "millisecond" ? quantity : 0) + ); + return newDate; +}; + +export const startOfDate = (date: Date, scale: DateHelperScales) => { + const scores = [ + "millisecond", + "second", + "minute", + "hour", + "day", + "month", + "year", + ]; + + const shouldReset = (_scale: DateHelperScales) => { + const maxScore = scores.indexOf(scale); + return scores.indexOf(_scale) <= maxScore; + }; + const newDate = new Date( + date.getFullYear(), + shouldReset("year") ? 0 : date.getMonth(), + shouldReset("month") ? 1 : date.getDate(), + shouldReset("day") ? 0 : date.getHours(), + shouldReset("hour") ? 0 : date.getMinutes(), + shouldReset("minute") ? 0 : date.getSeconds(), + shouldReset("second") ? 0 : date.getMilliseconds() + ); + return newDate; +}; + +export const ganttDateRange = ( + tasks: Task[], + viewMode: ViewMode, + preStepsCount: number +) => { + let newStartDate: Date = tasks[0].start; + let newEndDate: Date = tasks[0].start; + for (const task of tasks) { + if (task.start < newStartDate) { + newStartDate = task.start; + } + if (task.end > newEndDate) { + newEndDate = task.end; + } + } + switch (viewMode) { + case ViewMode.Year: + newStartDate = addToDate(newStartDate, -1, "year"); + newStartDate = startOfDate(newStartDate, "year"); + newEndDate = addToDate(newEndDate, 1, "year"); + newEndDate = startOfDate(newEndDate, "year"); + break; + case ViewMode.QuarterYear: + newStartDate = addToDate(newStartDate, -3, "month"); + newStartDate = startOfDate(newStartDate, "month"); + newEndDate = addToDate(newEndDate, 3, "year"); + newEndDate = startOfDate(newEndDate, "year"); + break; + case ViewMode.Month: + newStartDate = addToDate(newStartDate, -1 * preStepsCount, "month"); + newStartDate = startOfDate(newStartDate, "month"); + newEndDate = addToDate(newEndDate, 1, "year"); + newEndDate = startOfDate(newEndDate, "year"); + break; + case ViewMode.Week: + newStartDate = startOfDate(newStartDate, "day"); + newStartDate = addToDate( + getMonday(newStartDate), + -7 * preStepsCount, + "day" + ); + newEndDate = startOfDate(newEndDate, "day"); + newEndDate = addToDate(newEndDate, 1.5, "month"); + break; + case ViewMode.Day: + newStartDate = startOfDate(newStartDate, "day"); + newStartDate = addToDate(newStartDate, -1 * preStepsCount, "day"); + newEndDate = startOfDate(newEndDate, "day"); + newEndDate = addToDate(newEndDate, 19, "day"); + break; + case ViewMode.QuarterDay: + newStartDate = startOfDate(newStartDate, "day"); + newStartDate = addToDate(newStartDate, -1 * preStepsCount, "day"); + newEndDate = startOfDate(newEndDate, "day"); + newEndDate = addToDate(newEndDate, 66, "hour"); // 24(1 day)*3 - 6 + break; + case ViewMode.HalfDay: + newStartDate = startOfDate(newStartDate, "day"); + newStartDate = addToDate(newStartDate, -1 * preStepsCount, "day"); + newEndDate = startOfDate(newEndDate, "day"); + newEndDate = addToDate(newEndDate, 108, "hour"); // 24(1 day)*5 - 12 + break; + case ViewMode.Hour: + newStartDate = startOfDate(newStartDate, "hour"); + newStartDate = addToDate(newStartDate, -1 * preStepsCount, "hour"); + newEndDate = startOfDate(newEndDate, "day"); + newEndDate = addToDate(newEndDate, 1, "day"); + break; + } + return [newStartDate, newEndDate]; +}; + +export const seedDates = ( + startDate: Date, + endDate: Date, + viewMode: ViewMode +) => { + let currentDate: Date = new Date(startDate); + const dates: Date[] = [currentDate]; + while (currentDate < endDate) { + switch (viewMode) { + case ViewMode.Year: + currentDate = addToDate(currentDate, 1, "year"); + break; + case ViewMode.QuarterYear: + currentDate = addToDate(currentDate, 3, "month"); + break; + case ViewMode.Month: + currentDate = addToDate(currentDate, 1, "month"); + break; + case ViewMode.Week: + currentDate = addToDate(currentDate, 7, "day"); + break; + case ViewMode.Day: + currentDate = addToDate(currentDate, 1, "day"); + break; + case ViewMode.HalfDay: + currentDate = addToDate(currentDate, 12, "hour"); + break; + case ViewMode.QuarterDay: + currentDate = addToDate(currentDate, 6, "hour"); + break; + case ViewMode.Hour: + currentDate = addToDate(currentDate, 1, "hour"); + break; + } + dates.push(currentDate); + } + return dates; +}; + +export const getLocaleMonth = (date: Date, locale: string) => { + let bottomValue = getCachedDateTimeFormat(locale, { + month: "long", + }).format(date); + bottomValue = bottomValue.replace( + bottomValue[0], + bottomValue[0].toLocaleUpperCase() + ); + return bottomValue; +}; + +export const getLocalDayOfWeek = ( + date: Date, + locale: string, + format?: "long" | "short" | "narrow" | undefined +) => { + let bottomValue = getCachedDateTimeFormat(locale, { + weekday: format, + }).format(date); + bottomValue = bottomValue.replace( + bottomValue[0], + bottomValue[0].toLocaleUpperCase() + ); + return bottomValue; +}; + +/** + * Returns monday of current week + * @param date date for modify + */ +const getMonday = (date: Date) => { + const day = date.getDay(); + const diff = date.getDate() - day + (day === 0 ? -6 : 1); // adjust when day is sunday + return new Date(date.setDate(diff)); +}; + +export const getWeekNumberISO8601 = (date: Date) => { + const tmpDate = new Date(date.valueOf()); + const dayNumber = (tmpDate.getDay() + 6) % 7; + tmpDate.setDate(tmpDate.getDate() - dayNumber + 3); + const firstThursday = tmpDate.valueOf(); + tmpDate.setMonth(0, 1); + if (tmpDate.getDay() !== 4) { + tmpDate.setMonth(0, 1 + ((4 - tmpDate.getDay() + 7) % 7)); + } + const weekNumber = ( + 1 + Math.ceil((firstThursday - tmpDate.valueOf()) / 604800000) + ).toString(); + + if (weekNumber.length === 1) { + return `0${weekNumber}`; + } else { + return weekNumber; + } +}; + +export const getDaysInMonth = (month: number, year: number) => { + return new Date(year, month + 1, 0).getDate(); +}; diff --git a/application/frontend/src/helpers/other-helper.ts b/application/frontend/src/helpers/other-helper.ts new file mode 100644 index 0000000..afd5a36 --- /dev/null +++ b/application/frontend/src/helpers/other-helper.ts @@ -0,0 +1,61 @@ +import { BarTask } from "../types/bar-task"; +import { Task } from "../types/public-types"; + +export function isKeyboardEvent( + event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent +): event is React.KeyboardEvent { + return (event as React.KeyboardEvent).key !== undefined; +} + +export function isMouseEvent( + event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent +): event is React.MouseEvent { + return (event as React.MouseEvent).clientX !== undefined; +} + +export function isBarTask(task: Task | BarTask): task is BarTask { + return (task as BarTask).x1 !== undefined; +} + +export function removeHiddenTasks(tasks: Task[]) { + const groupedTasks = tasks.filter( + t => t.hideChildren && t.type === "project" + ); + if (groupedTasks.length > 0) { + for (let i = 0; groupedTasks.length > i; i++) { + const groupedTask = groupedTasks[i]; + const children = getChildren(tasks, groupedTask); + tasks = tasks.filter(t => children.indexOf(t) === -1); + } + } + return tasks; +} + +function getChildren(taskList: Task[], task: Task) { + let tasks: Task[] = []; + if (task.type !== "project") { + tasks = taskList.filter( + t => t.dependencies && t.dependencies.indexOf(task.id) !== -1 + ); + } else { + tasks = taskList.filter(t => t.project && t.project === task.id); + } + var taskChildren: Task[] = []; + tasks.forEach(t => { + taskChildren.push(...getChildren(taskList, t)); + }) + tasks = tasks.concat(tasks, taskChildren); + return tasks; +} + +export const sortTasks = (taskA: Task, taskB: Task) => { + const orderA = taskA.displayOrder || Number.MAX_VALUE; + const orderB = taskB.displayOrder || Number.MAX_VALUE; + if (orderA > orderB) { + return 1; + } else if (orderA < orderB) { + return -1; + } else { + return 0; + } +}; diff --git a/application/frontend/src/lib/gantt/gantt.tsx b/application/frontend/src/lib/gantt/gantt.tsx index b90483f..5aaaf65 100644 --- a/application/frontend/src/lib/gantt/gantt.tsx +++ b/application/frontend/src/lib/gantt/gantt.tsx @@ -1,28 +1,23 @@ -import React, { - useState, - SyntheticEvent, - useRef, - useEffect, - useMemo, -} from "react"; -import { ViewMode, GanttProps, Task } from "../../types/public-types"; -import { GridProps } from "../grid/grid"; -import { ganttDateRange, seedDates } from "../../helpers/date-helper"; -import { CalendarProps } from "../calendar/calendar"; -import { TaskGanttContentProps } from "./task-gantt-content"; -import { TaskListHeaderDefault } from "../task-list/task-list-header"; -import { TaskListTableDefault } from "../task-list/task-list-table"; -import { StandardTooltipContent, Tooltip } from "../other/tooltip"; -import { VerticalScroll } from "../other/vertical-scroll"; -import { TaskListProps, TaskList } from "../task-list/task-list"; -import { TaskGantt } from "./task-gantt"; -import { BarTask } from "../../types/bar-task"; -import { convertToBarTasks } from "../../helpers/bar-helper"; -import { GanttEvent } from "../../types/gantt-task-actions"; -import { DateSetup } from "../../types/date-setup"; -import { HorizontalScroll } from "../other/horizontal-scroll"; -import { removeHiddenTasks, sortTasks } from "../../helpers/other-helper"; -import styles from "./gantt.module.css"; +import React, { SyntheticEvent, useEffect, useMemo, useRef, useState } from "react" + +import { convertToBarTasks } from "../../helpers/bar-helper" +import { ganttDateRange, seedDates } from "../../helpers/date-helper" +import { removeHiddenTasks, sortTasks } from "../../helpers/other-helper" +import { BarTask } from "../../types/bar-task" +import { DateSetup } from "../../types/date-setup" +import { GanttEvent } from "../../types/gantt-task-actions" +import { GanttProps, Task, ViewMode } from "../../types/public-types" +import { CalendarProps } from "../calendar/calendar" +import { GridProps } from "../grid/grid" +import { HorizontalScroll } from "../other/horizontal-scroll" +import { StandardTooltipContent, Tooltip } from "../other/tooltip" +import { VerticalScroll } from "../other/vertical-scroll" +import { TaskList, TaskListProps } from "../task-list/task-list" +import { TaskListHeaderDefault } from "../task-list/task-list-header" +import { TaskListTableDefault } from "../task-list/task-list-table" +import styles from "./gantt.module.css" +import { TaskGantt } from "./task-gantt" +import { TaskGanttContentProps } from "./task-gantt-content" export const Gantt: React.FunctionComponent = ({ tasks, @@ -66,60 +61,51 @@ export const Gantt: React.FunctionComponent = ({ onSelect, onExpanderClick, }) => { - const wrapperRef = useRef(null); - const taskListRef = useRef(null); + const wrapperRef = useRef(null) + const taskListRef = useRef(null) const [dateSetup, setDateSetup] = useState(() => { - const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount); - return { viewMode, dates: seedDates(startDate, endDate, viewMode) }; - }); - const [currentViewDate, setCurrentViewDate] = useState( - undefined - ); + const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount) + return { viewMode, dates: seedDates(startDate, endDate, viewMode) } + }) + const [currentViewDate, setCurrentViewDate] = useState(undefined) - const [taskListWidth, setTaskListWidth] = useState(0); - const [svgContainerWidth, setSvgContainerWidth] = useState(0); - const [svgContainerHeight, setSvgContainerHeight] = useState(ganttHeight); - const [barTasks, setBarTasks] = useState([]); + const [taskListWidth, setTaskListWidth] = useState(0) + const [svgContainerWidth, setSvgContainerWidth] = useState(0) + const [svgContainerHeight, setSvgContainerHeight] = useState(ganttHeight) + const [barTasks, setBarTasks] = useState([]) const [ganttEvent, setGanttEvent] = useState({ action: "", - }); - const taskHeight = useMemo( - () => (rowHeight * barFill) / 100, - [rowHeight, barFill] - ); + }) + const taskHeight = useMemo(() => (rowHeight * barFill) / 100, [rowHeight, barFill]) - const [selectedTask, setSelectedTask] = useState(); - const [failedTask, setFailedTask] = useState(null); + const [selectedTask, setSelectedTask] = useState() + const [failedTask, setFailedTask] = useState(null) - const svgWidth = dateSetup.dates.length * columnWidth; - const ganttFullHeight = barTasks.length * rowHeight; + const svgWidth = dateSetup.dates.length * columnWidth + const ganttFullHeight = barTasks.length * rowHeight - const [scrollY, setScrollY] = useState(0); - const [scrollX, setScrollX] = useState(-1); - const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false); + const [scrollY, setScrollY] = useState(0) + const [scrollX, setScrollX] = useState(-1) + const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false) // task change events useEffect(() => { - let filteredTasks: Task[]; + let filteredTasks: Task[] if (onExpanderClick) { - filteredTasks = removeHiddenTasks(tasks); + filteredTasks = removeHiddenTasks(tasks) } else { - filteredTasks = tasks; + filteredTasks = tasks } - filteredTasks = filteredTasks.sort(sortTasks); - const [startDate, endDate] = ganttDateRange( - filteredTasks, - viewMode, - preStepsCount - ); - let newDates = seedDates(startDate, endDate, viewMode); + filteredTasks = filteredTasks.sort(sortTasks) + const [startDate, endDate] = ganttDateRange(filteredTasks, viewMode, preStepsCount) + let newDates = seedDates(startDate, endDate, viewMode) if (rtl) { - newDates = newDates.reverse(); + newDates = newDates.reverse() if (scrollX === -1) { - setScrollX(newDates.length * columnWidth); + setScrollX(newDates.length * columnWidth) } } - setDateSetup({ dates: newDates, viewMode }); + setDateSetup({ dates: newDates, viewMode }) setBarTasks( convertToBarTasks( filteredTasks, @@ -139,9 +125,9 @@ export const Gantt: React.FunctionComponent = ({ projectBackgroundColor, projectBackgroundSelectedColor, milestoneBackgroundColor, - milestoneBackgroundSelectedColor - ) - ); + milestoneBackgroundSelectedColor, + ), + ) }, [ tasks, viewMode, @@ -164,7 +150,7 @@ export const Gantt: React.FunctionComponent = ({ rtl, scrollX, onExpanderClick, - ]); + ]) useEffect(() => { if ( @@ -172,18 +158,18 @@ export const Gantt: React.FunctionComponent = ({ ((viewDate && !currentViewDate) || (viewDate && currentViewDate?.valueOf() !== viewDate.valueOf())) ) { - const dates = dateSetup.dates; + const dates = dateSetup.dates const index = dates.findIndex( (d, i) => viewDate.valueOf() >= d.valueOf() && i + 1 !== dates.length && - viewDate.valueOf() < dates[i + 1].valueOf() - ); + viewDate.valueOf() < dates[i + 1].valueOf(), + ) if (index === -1) { - return; + return } - setCurrentViewDate(viewDate); - setScrollX(columnWidth * index); + setCurrentViewDate(viewDate) + setScrollX(columnWidth * index) } }, [ viewDate, @@ -193,21 +179,21 @@ export const Gantt: React.FunctionComponent = ({ viewMode, currentViewDate, setCurrentViewDate, - ]); + ]) useEffect(() => { - const { changedTask, action } = ganttEvent; + const { changedTask, action } = ganttEvent if (changedTask) { if (action === "delete") { - setGanttEvent({ action: "" }); - setBarTasks(barTasks.filter(t => t.id !== changedTask.id)); + setGanttEvent({ action: "" }) + setBarTasks(barTasks.filter((t) => t.id !== changedTask.id)) } else if ( action === "move" || action === "end" || action === "start" || action === "progress" ) { - const prevStateTask = barTasks.find(t => t.id === changedTask.id); + const prevStateTask = barTasks.find((t) => t.id === changedTask.id) if ( prevStateTask && (prevStateTask.start.getTime() !== changedTask.start.getTime() || @@ -215,178 +201,166 @@ export const Gantt: React.FunctionComponent = ({ prevStateTask.progress !== changedTask.progress) ) { // actions for change - const newTaskList = barTasks.map(t => - t.id === changedTask.id ? changedTask : t - ); - setBarTasks(newTaskList); + const newTaskList = barTasks.map((t) => (t.id === changedTask.id ? changedTask : t)) + setBarTasks(newTaskList) } } } - }, [ganttEvent, barTasks]); + }, [ganttEvent, barTasks]) useEffect(() => { if (failedTask) { - setBarTasks(barTasks.map(t => (t.id !== failedTask.id ? t : failedTask))); - setFailedTask(null); + setBarTasks(barTasks.map((t) => (t.id !== failedTask.id ? t : failedTask))) + setFailedTask(null) } - }, [failedTask, barTasks]); + }, [failedTask, barTasks]) useEffect(() => { if (!listCellWidth) { - setTaskListWidth(0); + setTaskListWidth(0) } if (taskListRef.current) { - setTaskListWidth(taskListRef.current.offsetWidth); + setTaskListWidth(taskListRef.current.offsetWidth) } - }, [taskListRef, listCellWidth]); + }, [taskListRef, listCellWidth]) useEffect(() => { if (wrapperRef.current) { - setSvgContainerWidth(wrapperRef.current.offsetWidth - taskListWidth); + setSvgContainerWidth(wrapperRef.current.offsetWidth - taskListWidth) } - }, [wrapperRef, taskListWidth]); + }, [wrapperRef, taskListWidth]) useEffect(() => { if (ganttHeight) { - setSvgContainerHeight(ganttHeight + headerHeight); + setSvgContainerHeight(ganttHeight + headerHeight) } else { - setSvgContainerHeight(tasks.length * rowHeight + headerHeight); + setSvgContainerHeight(tasks.length * rowHeight + headerHeight) } - }, [ganttHeight, tasks, headerHeight, rowHeight]); + }, [ganttHeight, tasks, headerHeight, rowHeight]) // scroll events useEffect(() => { const handleWheel = (event: WheelEvent) => { if (event.shiftKey || event.deltaX) { - const scrollMove = event.deltaX ? event.deltaX : event.deltaY; - let newScrollX = scrollX + scrollMove; + const scrollMove = event.deltaX ? event.deltaX : event.deltaY + let newScrollX = scrollX + scrollMove if (newScrollX < 0) { - newScrollX = 0; + newScrollX = 0 } else if (newScrollX > svgWidth) { - newScrollX = svgWidth; + newScrollX = svgWidth } - setScrollX(newScrollX); - event.preventDefault(); + setScrollX(newScrollX) + event.preventDefault() } else if (ganttHeight) { - let newScrollY = scrollY + event.deltaY; + let newScrollY = scrollY + event.deltaY if (newScrollY < 0) { - newScrollY = 0; + newScrollY = 0 } else if (newScrollY > ganttFullHeight - ganttHeight) { - newScrollY = ganttFullHeight - ganttHeight; + newScrollY = ganttFullHeight - ganttHeight } if (newScrollY !== scrollY) { - setScrollY(newScrollY); - event.preventDefault(); + setScrollY(newScrollY) + event.preventDefault() } } - setIgnoreScrollEvent(true); - }; + setIgnoreScrollEvent(true) + } // subscribe if scroll is necessary wrapperRef.current?.addEventListener("wheel", handleWheel, { passive: false, - }); + }) return () => { - wrapperRef.current?.removeEventListener("wheel", handleWheel); - }; - }, [ - wrapperRef, - scrollY, - scrollX, - ganttHeight, - svgWidth, - rtl, - ganttFullHeight, - ]); + wrapperRef.current?.removeEventListener("wheel", handleWheel) + } + }, [wrapperRef, scrollY, scrollX, ganttHeight, svgWidth, rtl, ganttFullHeight]) const handleScrollY = (event: SyntheticEvent) => { if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) { - setScrollY(event.currentTarget.scrollTop); - setIgnoreScrollEvent(true); + setScrollY(event.currentTarget.scrollTop) + setIgnoreScrollEvent(true) } else { - setIgnoreScrollEvent(false); + setIgnoreScrollEvent(false) } - }; + } const handleScrollX = (event: SyntheticEvent) => { if (scrollX !== event.currentTarget.scrollLeft && !ignoreScrollEvent) { - setScrollX(event.currentTarget.scrollLeft); - setIgnoreScrollEvent(true); + setScrollX(event.currentTarget.scrollLeft) + setIgnoreScrollEvent(true) } else { - setIgnoreScrollEvent(false); + setIgnoreScrollEvent(false) } - }; + } /** * Handles arrow keys events and transform it to new scroll */ const handleKeyDown = (event: React.KeyboardEvent) => { - event.preventDefault(); - let newScrollY = scrollY; - let newScrollX = scrollX; - let isX = true; + event.preventDefault() + let newScrollY = scrollY + let newScrollX = scrollX + let isX = true switch (event.key) { case "Down": // IE/Edge specific value case "ArrowDown": - newScrollY += rowHeight; - isX = false; - break; + newScrollY += rowHeight + isX = false + break case "Up": // IE/Edge specific value case "ArrowUp": - newScrollY -= rowHeight; - isX = false; - break; + newScrollY -= rowHeight + isX = false + break case "Left": case "ArrowLeft": - newScrollX -= columnWidth; - break; + newScrollX -= columnWidth + break case "Right": // IE/Edge specific value case "ArrowRight": - newScrollX += columnWidth; - break; + newScrollX += columnWidth + break } if (isX) { if (newScrollX < 0) { - newScrollX = 0; + newScrollX = 0 } else if (newScrollX > svgWidth) { - newScrollX = svgWidth; + newScrollX = svgWidth } - setScrollX(newScrollX); + setScrollX(newScrollX) } else { if (newScrollY < 0) { - newScrollY = 0; + newScrollY = 0 } else if (newScrollY > ganttFullHeight - ganttHeight) { - newScrollY = ganttFullHeight - ganttHeight; + newScrollY = ganttFullHeight - ganttHeight } - setScrollY(newScrollY); + setScrollY(newScrollY) } - setIgnoreScrollEvent(true); - }; + setIgnoreScrollEvent(true) + } /** * Task select event */ const handleSelectedTask = (taskId: string) => { - const newSelectedTask = barTasks.find(t => t.id === taskId); - const oldSelectedTask = barTasks.find( - t => !!selectedTask && t.id === selectedTask.id - ); + const newSelectedTask = barTasks.find((t) => t.id === taskId) + const oldSelectedTask = barTasks.find((t) => !!selectedTask && t.id === selectedTask.id) if (onSelect) { if (oldSelectedTask) { - onSelect(oldSelectedTask, false); + onSelect(oldSelectedTask, false) } if (newSelectedTask) { - onSelect(newSelectedTask, true); + onSelect(newSelectedTask, true) } } - setSelectedTask(newSelectedTask); - }; + setSelectedTask(newSelectedTask) + } const handleExpanderClick = (task: Task) => { if (onExpanderClick && task.hideChildren !== undefined) { - onExpanderClick({ ...task, hideChildren: !task.hideChildren }); + onExpanderClick({ ...task, hideChildren: !task.hideChildren }) } - }; + } const gridProps: GridProps = { columnWidth, svgWidth, @@ -395,7 +369,7 @@ export const Gantt: React.FunctionComponent = ({ dates: dateSetup.dates, todayColor, rtl, - }; + } const calendarProps: CalendarProps = { dateSetup, locale, @@ -405,7 +379,7 @@ export const Gantt: React.FunctionComponent = ({ fontFamily, fontSize, rtl, - }; + } const barProps: TaskGanttContentProps = { tasks: barTasks, dates: dateSetup.dates, @@ -429,7 +403,7 @@ export const Gantt: React.FunctionComponent = ({ onDoubleClick, onClick, onDelete, - }; + } const tableProps: TaskListProps = { rowHeight, @@ -448,15 +422,10 @@ export const Gantt: React.FunctionComponent = ({ onExpanderClick: handleExpanderClick, TaskListHeader, TaskListTable, - }; + } return (
-
+
{listCellWidth && } = ({ onScroll={handleScrollX} />
- ); -}; + ) +} diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index e0c1028..ac18621 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -7,10 +7,11 @@ import stack from "src/assets/stack.svg" import CreateReportButton from "src/components/CreateReportButton/CreateReportButton" import { NavButton } from "src/components/NavButton" import ReportOverlay from "src/components/ReportOverlay/ReportOverlay" -import ReportTimeline from "src/components/ReportTimeline/ReportTimeline" import ReportsTable from "src/components/ReportsTable/ReportsTable" import { SearchBar } from "src/components/SearchBar" import { ViewModeToggle } from "src/components/ViewModeToggle" +import { Gantt } from "src/lib/gantt/gantt" +import { Task, ViewMode } from "src/types/public-types" import { Container, @@ -69,6 +70,14 @@ const reports: Report[] = [ }, ] +const tasks: Task[] = reports.map((report) => ({ + id: report.id.toString(), + name: report.name, + start: new Date(report.startDate), + end: new Date(report.endDate), + progress: 0, +})) + export const ReportPage: React.FC = () => { const { t } = useTranslation() const [searchQuery, setSearchQuery] = useState("") @@ -99,6 +108,34 @@ export const ReportPage: React.FC = () => { report.name.toLowerCase().includes(searchQuery.toLowerCase()), ) + const handleDateChange = (taskId: string, newDate: Date) => { + console.log(`Date changed for task ${taskId}: ${newDate}`) + } + + const handleProgressChange = (taskId: string, newProgress: number) => { + console.log(`Progress changed for task ${taskId}: ${newProgress}`) + } + + const handleDoubleClick = (taskId: string) => { + console.log(`Task ${taskId} double-clicked`) + } + + const handleClick = (taskId: string) => { + console.log(`Task ${taskId} clicked`) + } + + const handleDelete = (taskId: string) => { + console.log(`Task ${taskId} deleted`) + } + + const handleSelect = (task: Task, isSelected: boolean) => { + console.log(`Task ${task.id} ${isSelected ? "selected" : "deselected"}`) + } + + const handleExpanderClick = (task: Task) => { + console.log(`Expander clicked for task ${task.id}`) + } + const minDate = new Date(Math.min(...reports.map((r) => new Date(r.startDate).getTime()))) const maxDate = new Date(Math.max(...reports.map((r) => new Date(r.endDate).getTime()))) @@ -141,7 +178,67 @@ export const ReportPage: React.FC = () => { {viewMode === "list" ? ( ) : ( - +
Tooltip for {task.name}
} + TaskListHeader={({ rowHeight }) =>
Header
} + TaskListTable={({ tasks, rowHeight }) => ( +
+ + + + + + + + + {tasks.map((task) => ( + + + + + + ))} + +
NameStartEnd
{task.name}{task.start.toDateString()}{task.end.toDateString()}
+ )} + onDateChange={handleDateChange} + onProgressChange={handleProgressChange} + onDoubleClick={handleDoubleClick} + onClick={handleClick} + onDelete={handleDelete} + onSelect={handleSelect} + onExpanderClick={handleExpanderClick} + /> )} diff --git a/application/frontend/src/types/bar-task.ts b/application/frontend/src/types/bar-task.ts new file mode 100644 index 0000000..1be75e8 --- /dev/null +++ b/application/frontend/src/types/bar-task.ts @@ -0,0 +1,23 @@ +import { Task, TaskType } from "./public-types"; + +export interface BarTask extends Task { + index: number; + typeInternal: TaskTypeInternal; + x1: number; + x2: number; + y: number; + height: number; + progressX: number; + progressWidth: number; + barCornerRadius: number; + handleWidth: number; + barChildren: BarTask[]; + styles: { + backgroundColor: string; + backgroundSelectedColor: string; + progressColor: string; + progressSelectedColor: string; + }; +} + +export type TaskTypeInternal = TaskType | "smalltask"; diff --git a/application/frontend/src/types/date-setup.ts b/application/frontend/src/types/date-setup.ts new file mode 100644 index 0000000..81115ec --- /dev/null +++ b/application/frontend/src/types/date-setup.ts @@ -0,0 +1,6 @@ +import { ViewMode } from "./public-types"; + +export interface DateSetup { + dates: Date[]; + viewMode: ViewMode; +} diff --git a/application/frontend/src/types/gantt-task-actions.ts b/application/frontend/src/types/gantt-task-actions.ts new file mode 100644 index 0000000..01e1292 --- /dev/null +++ b/application/frontend/src/types/gantt-task-actions.ts @@ -0,0 +1,18 @@ +import { BarTask } from "./bar-task"; + +export type BarMoveAction = "progress" | "end" | "start" | "move"; +export type GanttContentMoveAction = + | "mouseenter" + | "mouseleave" + | "delete" + | "dblclick" + | "click" + | "select" + | "" + | BarMoveAction; + +export type GanttEvent = { + changedTask?: BarTask; + originalSelectedTask?: BarTask; + action: GanttContentMoveAction; +}; diff --git a/application/frontend/src/types/public-types.ts b/application/frontend/src/types/public-types.ts new file mode 100644 index 0000000..cc44ff1 --- /dev/null +++ b/application/frontend/src/types/public-types.ts @@ -0,0 +1,145 @@ +export enum ViewMode { + Hour = "Hour", + QuarterDay = "Quarter Day", + HalfDay = "Half Day", + Day = "Day", + /** ISO-8601 week */ + Week = "Week", + Month = "Month", + QuarterYear = "QuarterYear", + Year = "Year", +} +export type TaskType = "task" | "milestone" | "project"; +export interface Task { + id: string; + type: TaskType; + name: string; + start: Date; + end: Date; + /** + * From 0 to 100 + */ + progress: number; + styles?: { + backgroundColor?: string; + backgroundSelectedColor?: string; + progressColor?: string; + progressSelectedColor?: string; + }; + isDisabled?: boolean; + project?: string; + dependencies?: string[]; + hideChildren?: boolean; + displayOrder?: number; +} + +export interface EventOption { + /** + * Time step value for date changes. + */ + timeStep?: number; + /** + * Invokes on bar select on unselect. + */ + onSelect?: (task: Task, isSelected: boolean) => void; + /** + * Invokes on bar double click. + */ + onDoubleClick?: (task: Task) => void; + /** + * Invokes on bar click. + */ + onClick?: (task: Task) => void; + /** + * Invokes on end and start time change. Chart undoes operation if method return false or error. + */ + onDateChange?: ( + task: Task, + children: Task[] + ) => void | boolean | Promise | Promise; + /** + * Invokes on progress change. Chart undoes operation if method return false or error. + */ + onProgressChange?: ( + task: Task, + children: Task[] + ) => void | boolean | Promise | Promise; + /** + * Invokes on delete selected task. Chart undoes operation if method return false or error. + */ + onDelete?: (task: Task) => void | boolean | Promise | Promise; + /** + * Invokes on expander on task list + */ + onExpanderClick?: (task: Task) => void; +} + +export interface DisplayOption { + viewMode?: ViewMode; + viewDate?: Date; + preStepsCount?: number; + /** + * Specifies the month name language. Able formats: ISO 639-2, Java Locale + */ + locale?: string; + rtl?: boolean; +} + +export interface StylingOption { + headerHeight?: number; + columnWidth?: number; + listCellWidth?: string; + rowHeight?: number; + ganttHeight?: number; + barCornerRadius?: number; + handleWidth?: number; + fontFamily?: string; + fontSize?: string; + /** + * How many of row width can be taken by task. + * From 0 to 100 + */ + barFill?: number; + barProgressColor?: string; + barProgressSelectedColor?: string; + barBackgroundColor?: string; + barBackgroundSelectedColor?: string; + projectProgressColor?: string; + projectProgressSelectedColor?: string; + projectBackgroundColor?: string; + projectBackgroundSelectedColor?: string; + milestoneBackgroundColor?: string; + milestoneBackgroundSelectedColor?: string; + arrowColor?: string; + arrowIndent?: number; + todayColor?: string; + TooltipContent?: React.FC<{ + task: Task; + fontSize: string; + fontFamily: string; + }>; + TaskListHeader?: React.FC<{ + headerHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + }>; + TaskListTable?: React.FC<{ + rowHeight: number; + rowWidth: string; + fontFamily: string; + fontSize: string; + locale: string; + tasks: Task[]; + selectedTaskId: string; + /** + * Sets selected task by id + */ + setSelectedTask: (taskId: string) => void; + onExpanderClick: (task: Task) => void; + }>; +} + +export interface GanttProps extends EventOption, DisplayOption, StylingOption { + tasks: Task[]; +} From 4f0f62e5d6ea39655b9844728fd6682c7c7365bc Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Fri, 30 Aug 2024 23:16:18 +0300 Subject: [PATCH 40/42] fix: change gantt`s header --- .../frontend/src/pages/report/Report.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index ac18621..94b9d44 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -70,12 +70,22 @@ const reports: Report[] = [ }, ] +const stageColors: { [key: string]: string } = { + Initial: "#4caf50", + Onboarding: "#ff9800", + "In progress": "#2196f3", + "In review": "#9e9e9e", + "In test": "#f44336", +} + const tasks: Task[] = reports.map((report) => ({ id: report.id.toString(), name: report.name, start: new Date(report.startDate), end: new Date(report.endDate), progress: 0, + color: stageColors[report.stage] || "#9e9e9e", + type: "project", })) export const ReportPage: React.FC = () => { @@ -187,10 +197,10 @@ export const ReportPage: React.FC = () => { ganttHeight={500} viewMode={ViewMode.Week} preStepsCount={2} - locale="en-US" + locale="ru" //ochko1 barFill={70} barCornerRadius={5} - barProgressColor="#4a90e2" + barProgressColor="#007aff" //ochko2 barProgressSelectedColor="#007aff" barBackgroundColor="#d3d3d3" barBackgroundSelectedColor="#bfbfbf" @@ -210,22 +220,18 @@ export const ReportPage: React.FC = () => { todayColor="rgba(255, 0, 0, 0.2)" viewDate={new Date("2024-08-01")} TooltipContent={({ task }) =>
Tooltip for {task.name}
} - TaskListHeader={({ rowHeight }) =>
Header
} + TaskListHeader={({ rowHeight }) =>
Отчеты
} TaskListTable={({ tasks, rowHeight }) => ( - - - + {tasks.map((task) => ( - - ))} From 8d4fe22a86ded05173f95d0b45384e136b408473 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Mon, 2 Sep 2024 16:48:36 +0300 Subject: [PATCH 41/42] feat(frontend): add login page --- application/frontend/src/pages/project/Project.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index 4f1aca4..f653b50 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -44,7 +44,7 @@ export const ProjectPage: React.FC = () => { return ( - }> + }> {t("projectList")} From ee1404076b95d8db891afd95993edc1f29737621 Mon Sep 17 00:00:00 2001 From: Anastasiya Date: Mon, 2 Sep 2024 21:39:05 +0300 Subject: [PATCH 42/42] refactor(frontend): remove gant, fix env vars, change aliases --- application/frontend/package-lock.json | 859 +++++++++++------- application/frontend/package.json | 5 +- application/frontend/src/App.tsx | 1 + .../frontend/src/components/Header/Header.tsx | 8 +- .../frontend/src/components/Layout/Layout.tsx | 9 +- .../src/components/NavButton/NavButton.tsx | 2 +- .../components/ProfileCard/ProfileCard.tsx | 2 +- .../ReportTimeline/ReportTimeline.tsx | 1 - .../src/components/SearchBar/SearchBar.tsx | 2 - .../frontend/src/components/Theme/Theme.tsx | 4 +- .../src/components/Theme/Theme.types.ts | 4 +- .../ViewModeToggle/ViewModeToggle.tsx | 2 +- .../frontend/src/helpers/bar-helper.ts | 589 ------------ .../frontend/src/helpers/date-helper.ts | 241 ----- .../frontend/src/helpers/other-helper.ts | 61 -- .../src/lib/calendar/calendar.module.css | 31 - .../frontend/src/lib/calendar/calendar.tsx | 395 -------- .../src/lib/calendar/top-part-of-calendar.tsx | 41 - .../frontend/src/lib/gantt/gantt.module.css | 21 - application/frontend/src/lib/gantt/gantt.tsx | 474 ---------- .../src/lib/gantt/task-gantt-content.tsx | 302 ------ .../frontend/src/lib/gantt/task-gantt.tsx | 76 -- .../frontend/src/lib/grid/grid-body.tsx | 127 --- .../frontend/src/lib/grid/grid.module.css | 15 - application/frontend/src/lib/grid/grid.tsx | 11 - application/frontend/src/lib/other/arrow.tsx | 106 --- .../lib/other/horizontal-scroll.module.css | 33 - .../src/lib/other/horizontal-scroll.tsx | 34 - .../frontend/src/lib/other/tooltip.module.css | 30 - .../frontend/src/lib/other/tooltip.tsx | 145 --- .../src/lib/other/vertical-scroll.module.css | 27 - .../src/lib/other/vertical-scroll.tsx | 41 - .../src/lib/task-item/bar/bar-date-handle.tsx | 32 - .../src/lib/task-item/bar/bar-display.tsx | 65 -- .../lib/task-item/bar/bar-progress-handle.tsx | 19 - .../src/lib/task-item/bar/bar-small.tsx | 48 - .../src/lib/task-item/bar/bar.module.css | 21 - .../frontend/src/lib/task-item/bar/bar.tsx | 77 -- .../task-item/milestone/milestone.module.css | 8 - .../src/lib/task-item/milestone/milestone.tsx | 37 - .../lib/task-item/project/project.module.css | 13 - .../src/lib/task-item/project/project.tsx | 74 -- .../frontend/src/lib/task-item/task-item.tsx | 125 --- .../src/lib/task-item/task-list.module.css | 23 - .../lib/task-list/task-list-header.module.css | 23 - .../src/lib/task-list/task-list-header.tsx | 65 -- .../lib/task-list/task-list-table.module.css | 38 - .../src/lib/task-list/task-list-table.tsx | 115 --- .../frontend/src/lib/task-list/task-list.tsx | 95 -- application/frontend/src/locales/service.ts | 2 +- application/frontend/src/main.tsx | 10 +- application/frontend/src/pages/Home/Home.tsx | 6 +- .../frontend/src/pages/project/Project.tsx | 12 +- .../frontend/src/pages/report/Report.tsx | 137 +-- application/frontend/src/routes/routes.tsx | 11 +- application/frontend/src/types/vite-env.d.ts | 1 - application/frontend/src/vite-env.d.ts | 10 + application/frontend/tsconfig.app.json | 28 - application/frontend/tsconfig.json | 30 +- application/frontend/tsconfig.node.json | 26 - application/frontend/vite.config.ts | 17 +- 61 files changed, 607 insertions(+), 4260 deletions(-) delete mode 100644 application/frontend/src/helpers/bar-helper.ts delete mode 100644 application/frontend/src/helpers/date-helper.ts delete mode 100644 application/frontend/src/helpers/other-helper.ts delete mode 100644 application/frontend/src/lib/calendar/calendar.module.css delete mode 100644 application/frontend/src/lib/calendar/calendar.tsx delete mode 100644 application/frontend/src/lib/calendar/top-part-of-calendar.tsx delete mode 100644 application/frontend/src/lib/gantt/gantt.module.css delete mode 100644 application/frontend/src/lib/gantt/gantt.tsx delete mode 100644 application/frontend/src/lib/gantt/task-gantt-content.tsx delete mode 100644 application/frontend/src/lib/gantt/task-gantt.tsx delete mode 100644 application/frontend/src/lib/grid/grid-body.tsx delete mode 100644 application/frontend/src/lib/grid/grid.module.css delete mode 100644 application/frontend/src/lib/grid/grid.tsx delete mode 100644 application/frontend/src/lib/other/arrow.tsx delete mode 100644 application/frontend/src/lib/other/horizontal-scroll.module.css delete mode 100644 application/frontend/src/lib/other/horizontal-scroll.tsx delete mode 100644 application/frontend/src/lib/other/tooltip.module.css delete mode 100644 application/frontend/src/lib/other/tooltip.tsx delete mode 100644 application/frontend/src/lib/other/vertical-scroll.module.css delete mode 100644 application/frontend/src/lib/other/vertical-scroll.tsx delete mode 100644 application/frontend/src/lib/task-item/bar/bar-date-handle.tsx delete mode 100644 application/frontend/src/lib/task-item/bar/bar-display.tsx delete mode 100644 application/frontend/src/lib/task-item/bar/bar-progress-handle.tsx delete mode 100644 application/frontend/src/lib/task-item/bar/bar-small.tsx delete mode 100644 application/frontend/src/lib/task-item/bar/bar.module.css delete mode 100644 application/frontend/src/lib/task-item/bar/bar.tsx delete mode 100644 application/frontend/src/lib/task-item/milestone/milestone.module.css delete mode 100644 application/frontend/src/lib/task-item/milestone/milestone.tsx delete mode 100644 application/frontend/src/lib/task-item/project/project.module.css delete mode 100644 application/frontend/src/lib/task-item/project/project.tsx delete mode 100644 application/frontend/src/lib/task-item/task-item.tsx delete mode 100644 application/frontend/src/lib/task-item/task-list.module.css delete mode 100644 application/frontend/src/lib/task-list/task-list-header.module.css delete mode 100644 application/frontend/src/lib/task-list/task-list-header.tsx delete mode 100644 application/frontend/src/lib/task-list/task-list-table.module.css delete mode 100644 application/frontend/src/lib/task-list/task-list-table.tsx delete mode 100644 application/frontend/src/lib/task-list/task-list.tsx delete mode 100644 application/frontend/src/types/vite-env.d.ts create mode 100644 application/frontend/src/vite-env.d.ts delete mode 100644 application/frontend/tsconfig.app.json delete mode 100644 application/frontend/tsconfig.node.json diff --git a/application/frontend/package-lock.json b/application/frontend/package-lock.json index e884847..2b9c47f 100644 --- a/application/frontend/package-lock.json +++ b/application/frontend/package-lock.json @@ -43,9 +43,10 @@ "globals": "^15.9.0", "husky": "^9.1.4", "prettier": "^3.3.3", - "typescript": "^5.5.3", + "typescript": "^5.1.6", "typescript-eslint": "^8.0.0", - "vite": "^5.4.0" + "vite": "^4.2.0", + "vite-plugin-svgr": "^4.2.0" }, "engines": { "node": ">=21.1.0" @@ -632,30 +633,15 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -665,13 +651,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -681,13 +668,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -697,13 +685,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -713,13 +702,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -729,13 +719,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -745,13 +736,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -761,13 +753,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -777,13 +770,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -793,13 +787,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -809,13 +804,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -825,13 +821,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -841,13 +838,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -857,13 +855,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -873,13 +872,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -889,13 +889,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -905,13 +906,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -921,13 +923,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -937,13 +940,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -953,13 +957,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -969,13 +974,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -985,13 +991,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1667,213 +1674,28 @@ "node": ">=14.0.0" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", - "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", - "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", - "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", - "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", - "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", - "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", - "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", - "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", - "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", - "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", - "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", - "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", - "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", - "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", - "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", - "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", - "cpu": [ - "x64" - ], + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", "dev": true, - "optional": true, - "os": [ - "win32" - ] + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", @@ -2397,6 +2219,258 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, "node_modules/@swc/core": { "version": "1.7.6", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.6.tgz", @@ -2757,7 +2831,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "devOptional": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.4.1", @@ -3323,6 +3398,19 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -3795,6 +3883,17 @@ "csstype": "^3.0.2" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -3838,6 +3937,19 @@ "dev": true, "peer": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-ci": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.0.0.tgz", @@ -4028,11 +4140,12 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "devOptional": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -4040,29 +4153,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" } }, "node_modules/escalade": { @@ -4235,6 +4347,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -4495,6 +4614,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -5318,6 +5438,16 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5535,6 +5665,17 @@ "dev": true, "peer": true }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-emoji": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", @@ -9127,37 +9268,19 @@ } }, "node_modules/rollup": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", - "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "devOptional": true, - "dependencies": { - "@types/estree": "1.0.5" - }, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=18.0.0", + "node": ">=14.18.0", "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.20.0", - "@rollup/rollup-android-arm64": "4.20.0", - "@rollup/rollup-darwin-arm64": "4.20.0", - "@rollup/rollup-darwin-x64": "4.20.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", - "@rollup/rollup-linux-arm-musleabihf": "4.20.0", - "@rollup/rollup-linux-arm64-gnu": "4.20.0", - "@rollup/rollup-linux-arm64-musl": "4.20.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", - "@rollup/rollup-linux-riscv64-gnu": "4.20.0", - "@rollup/rollup-linux-s390x-gnu": "4.20.0", - "@rollup/rollup-linux-x64-gnu": "4.20.0", - "@rollup/rollup-linux-x64-musl": "4.20.0", - "@rollup/rollup-win32-arm64-msvc": "4.20.0", - "@rollup/rollup-win32-ia32-msvc": "4.20.0", - "@rollup/rollup-win32-x64-msvc": "4.20.0", "fsevents": "~2.3.2" } }, @@ -9698,6 +9821,17 @@ "node": ">=8" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -9982,6 +10116,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", @@ -10379,33 +10520,33 @@ } }, "node_modules/vite": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", - "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "devOptional": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.40", - "rollup": "^4.13.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.3" + "fsevents": "~2.3.2" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": ">= 14", "less": "*", "lightningcss": "^1.21.0", "sass": "*", - "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -10423,9 +10564,6 @@ "sass": { "optional": true }, - "sass-embedded": { - "optional": true - }, "stylus": { "optional": true }, @@ -10437,6 +10575,21 @@ } } }, + "node_modules/vite-plugin-svgr": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", + "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.5", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": "^2.6.0 || 3 || 4 || 5" + } + }, "node_modules/vite-tsconfig-paths": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz", diff --git a/application/frontend/package.json b/application/frontend/package.json index d9117e0..4ed80aa 100644 --- a/application/frontend/package.json +++ b/application/frontend/package.json @@ -54,9 +54,10 @@ "globals": "^15.9.0", "husky": "^9.1.4", "prettier": "^3.3.3", - "typescript": "^5.5.3", + "typescript": "^5.1.6", "typescript-eslint": "^8.0.0", - "vite": "^5.4.0" + "vite": "^4.2.0", + "vite-plugin-svgr": "^4.2.0" }, "plugins": [ "@semantic-release/commit-analyzer", diff --git a/application/frontend/src/App.tsx b/application/frontend/src/App.tsx index ed0915b..e96d378 100644 --- a/application/frontend/src/App.tsx +++ b/application/frontend/src/App.tsx @@ -1,3 +1,4 @@ +import React from "react" import { BrowserRouter } from "react-router-dom" import "@fontsource/roboto/300.css" diff --git a/application/frontend/src/components/Header/Header.tsx b/application/frontend/src/components/Header/Header.tsx index 0f5b50f..57b833d 100644 --- a/application/frontend/src/components/Header/Header.tsx +++ b/application/frontend/src/components/Header/Header.tsx @@ -1,3 +1,7 @@ +import { UserController } from "~api/controllers/UserController" +import { useTheme } from "~components/Theme" +import { ThemeEnum } from "~types/theme/enum" + import React, { useEffect } from "react" import { useTranslation } from "react-i18next" import { useNavigate } from "react-router-dom" @@ -5,10 +9,6 @@ import { useNavigate } from "react-router-dom" import { AccountCircleOutlined, MoreVertOutlined, NotificationsOutlined } from "@mui/icons-material" import { Button, IconButton, Switch, Tooltip, Typography } from "@mui/material" -import { UserController } from "src/api/controllers/UserController" -import { useTheme } from "src/components/Theme" -import { ThemeEnum } from "src/types/theme/enum" - import { Container, IconButtons, diff --git a/application/frontend/src/components/Layout/Layout.tsx b/application/frontend/src/components/Layout/Layout.tsx index 7ac3d94..7a76dab 100644 --- a/application/frontend/src/components/Layout/Layout.tsx +++ b/application/frontend/src/components/Layout/Layout.tsx @@ -1,10 +1,11 @@ +import { Header } from "~components/Header" +import { Theme } from "~components/Theme" +import i18nInstance from "~locales/service" + +import React from "react" import { I18nextProvider } from "react-i18next" import { Outlet, useLocation } from "react-router-dom" -import { Header } from "src/components/Header" -import { Theme } from "src/components/Theme" -import i18nInstance from "src/locales/service" - import Markup from "./Layout.styled" export const Layout = () => { diff --git a/application/frontend/src/components/NavButton/NavButton.tsx b/application/frontend/src/components/NavButton/NavButton.tsx index 8d8400c..9461a4b 100644 --- a/application/frontend/src/components/NavButton/NavButton.tsx +++ b/application/frontend/src/components/NavButton/NavButton.tsx @@ -5,7 +5,7 @@ import { StyledBreadcrumb } from "./NavButton.styled" interface NavButtonProps { to: string children: React.ReactNode - icon?: React.ReactNode + icon?: React.ReactElement } export const NavButton: React.FC = ({ to, children, icon }) => { diff --git a/application/frontend/src/components/ProfileCard/ProfileCard.tsx b/application/frontend/src/components/ProfileCard/ProfileCard.tsx index 31db2ad..f9a2019 100644 --- a/application/frontend/src/components/ProfileCard/ProfileCard.tsx +++ b/application/frontend/src/components/ProfileCard/ProfileCard.tsx @@ -40,7 +40,7 @@ export const ProfileCard: React.FC = ({ profile }) => { return ( - + {avatar ? ( ) => void - viewMode: "list" | "grid" | "timeline" - onToggleViewMode: () => void onClearSearch: () => void variant?: "standard" | "outlined" startIcon?: React.ReactNode diff --git a/application/frontend/src/components/Theme/Theme.tsx b/application/frontend/src/components/Theme/Theme.tsx index d315e51..efcb213 100644 --- a/application/frontend/src/components/Theme/Theme.tsx +++ b/application/frontend/src/components/Theme/Theme.tsx @@ -1,12 +1,12 @@ import { ThemeProvider } from "styled-components" +import { ThemeEnum } from "types/theme/enum" import { ReactNode, useState } from "react" +import React from "react" import { createTheme } from "@mui/material" import CssBaseline from "@mui/material/CssBaseline" -import { ThemeEnum } from "src/types/theme/enum" - import { ThemeContext } from "./Theme.hooks" interface ThemeProps { diff --git a/application/frontend/src/components/Theme/Theme.types.ts b/application/frontend/src/components/Theme/Theme.types.ts index 7994e64..b70cef5 100644 --- a/application/frontend/src/components/Theme/Theme.types.ts +++ b/application/frontend/src/components/Theme/Theme.types.ts @@ -1,6 +1,6 @@ -import { Theme } from "@mui/material" +import { ThemeEnum } from "types/theme/enum" -import { ThemeEnum } from "src/types/theme/enum" +import { Theme } from "@mui/material" export type ThemeContextTypes = { currentTheme: ThemeEnum diff --git a/application/frontend/src/components/ViewModeToggle/ViewModeToggle.tsx b/application/frontend/src/components/ViewModeToggle/ViewModeToggle.tsx index 01a4b88..896c7d5 100644 --- a/application/frontend/src/components/ViewModeToggle/ViewModeToggle.tsx +++ b/application/frontend/src/components/ViewModeToggle/ViewModeToggle.tsx @@ -5,7 +5,7 @@ import ViewModuleIcon from "@mui/icons-material/ViewModule" import { IconButton } from "@mui/material" interface ViewModeToggleProps { - viewMode: "list" | "grid" + viewMode: "list" | "grid" | "timeline" onToggleViewMode: () => void } diff --git a/application/frontend/src/helpers/bar-helper.ts b/application/frontend/src/helpers/bar-helper.ts deleted file mode 100644 index ba5f987..0000000 --- a/application/frontend/src/helpers/bar-helper.ts +++ /dev/null @@ -1,589 +0,0 @@ -import { Task } from "../types/public-types"; -import { BarTask, TaskTypeInternal } from "../types/bar-task"; -import { BarMoveAction } from "../types/gantt-task-actions"; - -export const convertToBarTasks = ( - tasks: Task[], - dates: Date[], - columnWidth: number, - rowHeight: number, - taskHeight: number, - barCornerRadius: number, - handleWidth: number, - rtl: boolean, - barProgressColor: string, - barProgressSelectedColor: string, - barBackgroundColor: string, - barBackgroundSelectedColor: string, - projectProgressColor: string, - projectProgressSelectedColor: string, - projectBackgroundColor: string, - projectBackgroundSelectedColor: string, - milestoneBackgroundColor: string, - milestoneBackgroundSelectedColor: string -) => { - let barTasks = tasks.map((t, i) => { - return convertToBarTask( - t, - i, - dates, - columnWidth, - rowHeight, - taskHeight, - barCornerRadius, - handleWidth, - rtl, - barProgressColor, - barProgressSelectedColor, - barBackgroundColor, - barBackgroundSelectedColor, - projectProgressColor, - projectProgressSelectedColor, - projectBackgroundColor, - projectBackgroundSelectedColor, - milestoneBackgroundColor, - milestoneBackgroundSelectedColor - ); - }); - - // set dependencies - barTasks = barTasks.map(task => { - const dependencies = task.dependencies || []; - for (let j = 0; j < dependencies.length; j++) { - const dependence = barTasks.findIndex( - value => value.id === dependencies[j] - ); - if (dependence !== -1) barTasks[dependence].barChildren.push(task); - } - return task; - }); - - return barTasks; -}; - -const convertToBarTask = ( - task: Task, - index: number, - dates: Date[], - columnWidth: number, - rowHeight: number, - taskHeight: number, - barCornerRadius: number, - handleWidth: number, - rtl: boolean, - barProgressColor: string, - barProgressSelectedColor: string, - barBackgroundColor: string, - barBackgroundSelectedColor: string, - projectProgressColor: string, - projectProgressSelectedColor: string, - projectBackgroundColor: string, - projectBackgroundSelectedColor: string, - milestoneBackgroundColor: string, - milestoneBackgroundSelectedColor: string -): BarTask => { - let barTask: BarTask; - switch (task.type) { - case "milestone": - barTask = convertToMilestone( - task, - index, - dates, - columnWidth, - rowHeight, - taskHeight, - barCornerRadius, - handleWidth, - milestoneBackgroundColor, - milestoneBackgroundSelectedColor - ); - break; - case "project": - barTask = convertToBar( - task, - index, - dates, - columnWidth, - rowHeight, - taskHeight, - barCornerRadius, - handleWidth, - rtl, - projectProgressColor, - projectProgressSelectedColor, - projectBackgroundColor, - projectBackgroundSelectedColor - ); - break; - default: - barTask = convertToBar( - task, - index, - dates, - columnWidth, - rowHeight, - taskHeight, - barCornerRadius, - handleWidth, - rtl, - barProgressColor, - barProgressSelectedColor, - barBackgroundColor, - barBackgroundSelectedColor - ); - break; - } - return barTask; -}; - -const convertToBar = ( - task: Task, - index: number, - dates: Date[], - columnWidth: number, - rowHeight: number, - taskHeight: number, - barCornerRadius: number, - handleWidth: number, - rtl: boolean, - barProgressColor: string, - barProgressSelectedColor: string, - barBackgroundColor: string, - barBackgroundSelectedColor: string -): BarTask => { - let x1: number; - let x2: number; - if (rtl) { - x2 = taskXCoordinateRTL(task.start, dates, columnWidth); - x1 = taskXCoordinateRTL(task.end, dates, columnWidth); - } else { - x1 = taskXCoordinate(task.start, dates, columnWidth); - x2 = taskXCoordinate(task.end, dates, columnWidth); - } - let typeInternal: TaskTypeInternal = task.type; - if (typeInternal === "task" && x2 - x1 < handleWidth * 2) { - typeInternal = "smalltask"; - x2 = x1 + handleWidth * 2; - } - - const [progressWidth, progressX] = progressWithByParams( - x1, - x2, - task.progress, - rtl - ); - const y = taskYCoordinate(index, rowHeight, taskHeight); - const hideChildren = task.type === "project" ? task.hideChildren : undefined; - - const styles = { - backgroundColor: barBackgroundColor, - backgroundSelectedColor: barBackgroundSelectedColor, - progressColor: barProgressColor, - progressSelectedColor: barProgressSelectedColor, - ...task.styles, - }; - return { - ...task, - typeInternal, - x1, - x2, - y, - index, - progressX, - progressWidth, - barCornerRadius, - handleWidth, - hideChildren, - height: taskHeight, - barChildren: [], - styles, - }; -}; - -const convertToMilestone = ( - task: Task, - index: number, - dates: Date[], - columnWidth: number, - rowHeight: number, - taskHeight: number, - barCornerRadius: number, - handleWidth: number, - milestoneBackgroundColor: string, - milestoneBackgroundSelectedColor: string -): BarTask => { - const x = taskXCoordinate(task.start, dates, columnWidth); - const y = taskYCoordinate(index, rowHeight, taskHeight); - - const x1 = x - taskHeight * 0.5; - const x2 = x + taskHeight * 0.5; - - const rotatedHeight = taskHeight / 1.414; - const styles = { - backgroundColor: milestoneBackgroundColor, - backgroundSelectedColor: milestoneBackgroundSelectedColor, - progressColor: "", - progressSelectedColor: "", - ...task.styles, - }; - return { - ...task, - end: task.start, - x1, - x2, - y, - index, - progressX: 0, - progressWidth: 0, - barCornerRadius, - handleWidth, - typeInternal: task.type, - progress: 0, - height: rotatedHeight, - hideChildren: undefined, - barChildren: [], - styles, - }; -}; - -const taskXCoordinate = (xDate: Date, dates: Date[], columnWidth: number) => { - const index = dates.findIndex(d => d.getTime() >= xDate.getTime()) - 1; - - const remainderMillis = xDate.getTime() - dates[index].getTime(); - const percentOfInterval = - remainderMillis / (dates[index + 1].getTime() - dates[index].getTime()); - const x = index * columnWidth + percentOfInterval * columnWidth; - return x; -}; -const taskXCoordinateRTL = ( - xDate: Date, - dates: Date[], - columnWidth: number -) => { - let x = taskXCoordinate(xDate, dates, columnWidth); - x += columnWidth; - return x; -}; -const taskYCoordinate = ( - index: number, - rowHeight: number, - taskHeight: number -) => { - const y = index * rowHeight + (rowHeight - taskHeight) / 2; - return y; -}; - -export const progressWithByParams = ( - taskX1: number, - taskX2: number, - progress: number, - rtl: boolean -) => { - const progressWidth = (taskX2 - taskX1) * progress * 0.01; - let progressX: number; - if (rtl) { - progressX = taskX2 - progressWidth; - } else { - progressX = taskX1; - } - return [progressWidth, progressX]; -}; - -export const progressByProgressWidth = ( - progressWidth: number, - barTask: BarTask -) => { - const barWidth = barTask.x2 - barTask.x1; - const progressPercent = Math.round((progressWidth * 100) / barWidth); - if (progressPercent >= 100) return 100; - else if (progressPercent <= 0) return 0; - else return progressPercent; -}; - -const progressByX = (x: number, task: BarTask) => { - if (x >= task.x2) return 100; - else if (x <= task.x1) return 0; - else { - const barWidth = task.x2 - task.x1; - const progressPercent = Math.round(((x - task.x1) * 100) / barWidth); - return progressPercent; - } -}; -const progressByXRTL = (x: number, task: BarTask) => { - if (x >= task.x2) return 0; - else if (x <= task.x1) return 100; - else { - const barWidth = task.x2 - task.x1; - const progressPercent = Math.round(((task.x2 - x) * 100) / barWidth); - return progressPercent; - } -}; - -export const getProgressPoint = ( - progressX: number, - taskY: number, - taskHeight: number -) => { - const point = [ - progressX - 5, - taskY + taskHeight, - progressX + 5, - taskY + taskHeight, - progressX, - taskY + taskHeight - 8.66, - ]; - return point.join(","); -}; - -const startByX = (x: number, xStep: number, task: BarTask) => { - if (x >= task.x2 - task.handleWidth * 2) { - x = task.x2 - task.handleWidth * 2; - } - const steps = Math.round((x - task.x1) / xStep); - const additionalXValue = steps * xStep; - const newX = task.x1 + additionalXValue; - return newX; -}; - -const endByX = (x: number, xStep: number, task: BarTask) => { - if (x <= task.x1 + task.handleWidth * 2) { - x = task.x1 + task.handleWidth * 2; - } - const steps = Math.round((x - task.x2) / xStep); - const additionalXValue = steps * xStep; - const newX = task.x2 + additionalXValue; - return newX; -}; - -const moveByX = (x: number, xStep: number, task: BarTask) => { - const steps = Math.round((x - task.x1) / xStep); - const additionalXValue = steps * xStep; - const newX1 = task.x1 + additionalXValue; - const newX2 = newX1 + task.x2 - task.x1; - return [newX1, newX2]; -}; - -const dateByX = ( - x: number, - taskX: number, - taskDate: Date, - xStep: number, - timeStep: number -) => { - let newDate = new Date(((x - taskX) / xStep) * timeStep + taskDate.getTime()); - newDate = new Date( - newDate.getTime() + - (newDate.getTimezoneOffset() - taskDate.getTimezoneOffset()) * 60000 - ); - return newDate; -}; - -/** - * Method handles event in real time(mousemove) and on finish(mouseup) - */ -export const handleTaskBySVGMouseEvent = ( - svgX: number, - action: BarMoveAction, - selectedTask: BarTask, - xStep: number, - timeStep: number, - initEventX1Delta: number, - rtl: boolean -): { isChanged: boolean; changedTask: BarTask } => { - let result: { isChanged: boolean; changedTask: BarTask }; - switch (selectedTask.type) { - case "milestone": - result = handleTaskBySVGMouseEventForMilestone( - svgX, - action, - selectedTask, - xStep, - timeStep, - initEventX1Delta - ); - break; - default: - result = handleTaskBySVGMouseEventForBar( - svgX, - action, - selectedTask, - xStep, - timeStep, - initEventX1Delta, - rtl - ); - break; - } - return result; -}; - -const handleTaskBySVGMouseEventForBar = ( - svgX: number, - action: BarMoveAction, - selectedTask: BarTask, - xStep: number, - timeStep: number, - initEventX1Delta: number, - rtl: boolean -): { isChanged: boolean; changedTask: BarTask } => { - const changedTask: BarTask = { ...selectedTask }; - let isChanged = false; - switch (action) { - case "progress": - if (rtl) { - changedTask.progress = progressByXRTL(svgX, selectedTask); - } else { - changedTask.progress = progressByX(svgX, selectedTask); - } - isChanged = changedTask.progress !== selectedTask.progress; - if (isChanged) { - const [progressWidth, progressX] = progressWithByParams( - changedTask.x1, - changedTask.x2, - changedTask.progress, - rtl - ); - changedTask.progressWidth = progressWidth; - changedTask.progressX = progressX; - } - break; - case "start": { - const newX1 = startByX(svgX, xStep, selectedTask); - changedTask.x1 = newX1; - isChanged = changedTask.x1 !== selectedTask.x1; - if (isChanged) { - if (rtl) { - changedTask.end = dateByX( - newX1, - selectedTask.x1, - selectedTask.end, - xStep, - timeStep - ); - } else { - changedTask.start = dateByX( - newX1, - selectedTask.x1, - selectedTask.start, - xStep, - timeStep - ); - } - const [progressWidth, progressX] = progressWithByParams( - changedTask.x1, - changedTask.x2, - changedTask.progress, - rtl - ); - changedTask.progressWidth = progressWidth; - changedTask.progressX = progressX; - } - break; - } - case "end": { - const newX2 = endByX(svgX, xStep, selectedTask); - changedTask.x2 = newX2; - isChanged = changedTask.x2 !== selectedTask.x2; - if (isChanged) { - if (rtl) { - changedTask.start = dateByX( - newX2, - selectedTask.x2, - selectedTask.start, - xStep, - timeStep - ); - } else { - changedTask.end = dateByX( - newX2, - selectedTask.x2, - selectedTask.end, - xStep, - timeStep - ); - } - const [progressWidth, progressX] = progressWithByParams( - changedTask.x1, - changedTask.x2, - changedTask.progress, - rtl - ); - changedTask.progressWidth = progressWidth; - changedTask.progressX = progressX; - } - break; - } - case "move": { - const [newMoveX1, newMoveX2] = moveByX( - svgX - initEventX1Delta, - xStep, - selectedTask - ); - isChanged = newMoveX1 !== selectedTask.x1; - if (isChanged) { - changedTask.start = dateByX( - newMoveX1, - selectedTask.x1, - selectedTask.start, - xStep, - timeStep - ); - changedTask.end = dateByX( - newMoveX2, - selectedTask.x2, - selectedTask.end, - xStep, - timeStep - ); - changedTask.x1 = newMoveX1; - changedTask.x2 = newMoveX2; - const [progressWidth, progressX] = progressWithByParams( - changedTask.x1, - changedTask.x2, - changedTask.progress, - rtl - ); - changedTask.progressWidth = progressWidth; - changedTask.progressX = progressX; - } - break; - } - } - return { isChanged, changedTask }; -}; - -const handleTaskBySVGMouseEventForMilestone = ( - svgX: number, - action: BarMoveAction, - selectedTask: BarTask, - xStep: number, - timeStep: number, - initEventX1Delta: number -): { isChanged: boolean; changedTask: BarTask } => { - const changedTask: BarTask = { ...selectedTask }; - let isChanged = false; - switch (action) { - case "move": { - const [newMoveX1, newMoveX2] = moveByX( - svgX - initEventX1Delta, - xStep, - selectedTask - ); - isChanged = newMoveX1 !== selectedTask.x1; - if (isChanged) { - changedTask.start = dateByX( - newMoveX1, - selectedTask.x1, - selectedTask.start, - xStep, - timeStep - ); - changedTask.end = changedTask.start; - changedTask.x1 = newMoveX1; - changedTask.x2 = newMoveX2; - } - break; - } - } - return { isChanged, changedTask }; -}; diff --git a/application/frontend/src/helpers/date-helper.ts b/application/frontend/src/helpers/date-helper.ts deleted file mode 100644 index 1b2a0f5..0000000 --- a/application/frontend/src/helpers/date-helper.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { Task, ViewMode } from "../types/public-types"; -import DateTimeFormatOptions = Intl.DateTimeFormatOptions; -import DateTimeFormat = Intl.DateTimeFormat; - -type DateHelperScales = - | "year" - | "month" - | "day" - | "hour" - | "minute" - | "second" - | "millisecond"; - -const intlDTCache = {}; -export const getCachedDateTimeFormat = ( - locString: string | string[], - opts: DateTimeFormatOptions = {} -): DateTimeFormat => { - const key = JSON.stringify([locString, opts]); - let dtf = intlDTCache[key]; - if (!dtf) { - dtf = new Intl.DateTimeFormat(locString, opts); - intlDTCache[key] = dtf; - } - return dtf; -}; - -export const addToDate = ( - date: Date, - quantity: number, - scale: DateHelperScales -) => { - const newDate = new Date( - date.getFullYear() + (scale === "year" ? quantity : 0), - date.getMonth() + (scale === "month" ? quantity : 0), - date.getDate() + (scale === "day" ? quantity : 0), - date.getHours() + (scale === "hour" ? quantity : 0), - date.getMinutes() + (scale === "minute" ? quantity : 0), - date.getSeconds() + (scale === "second" ? quantity : 0), - date.getMilliseconds() + (scale === "millisecond" ? quantity : 0) - ); - return newDate; -}; - -export const startOfDate = (date: Date, scale: DateHelperScales) => { - const scores = [ - "millisecond", - "second", - "minute", - "hour", - "day", - "month", - "year", - ]; - - const shouldReset = (_scale: DateHelperScales) => { - const maxScore = scores.indexOf(scale); - return scores.indexOf(_scale) <= maxScore; - }; - const newDate = new Date( - date.getFullYear(), - shouldReset("year") ? 0 : date.getMonth(), - shouldReset("month") ? 1 : date.getDate(), - shouldReset("day") ? 0 : date.getHours(), - shouldReset("hour") ? 0 : date.getMinutes(), - shouldReset("minute") ? 0 : date.getSeconds(), - shouldReset("second") ? 0 : date.getMilliseconds() - ); - return newDate; -}; - -export const ganttDateRange = ( - tasks: Task[], - viewMode: ViewMode, - preStepsCount: number -) => { - let newStartDate: Date = tasks[0].start; - let newEndDate: Date = tasks[0].start; - for (const task of tasks) { - if (task.start < newStartDate) { - newStartDate = task.start; - } - if (task.end > newEndDate) { - newEndDate = task.end; - } - } - switch (viewMode) { - case ViewMode.Year: - newStartDate = addToDate(newStartDate, -1, "year"); - newStartDate = startOfDate(newStartDate, "year"); - newEndDate = addToDate(newEndDate, 1, "year"); - newEndDate = startOfDate(newEndDate, "year"); - break; - case ViewMode.QuarterYear: - newStartDate = addToDate(newStartDate, -3, "month"); - newStartDate = startOfDate(newStartDate, "month"); - newEndDate = addToDate(newEndDate, 3, "year"); - newEndDate = startOfDate(newEndDate, "year"); - break; - case ViewMode.Month: - newStartDate = addToDate(newStartDate, -1 * preStepsCount, "month"); - newStartDate = startOfDate(newStartDate, "month"); - newEndDate = addToDate(newEndDate, 1, "year"); - newEndDate = startOfDate(newEndDate, "year"); - break; - case ViewMode.Week: - newStartDate = startOfDate(newStartDate, "day"); - newStartDate = addToDate( - getMonday(newStartDate), - -7 * preStepsCount, - "day" - ); - newEndDate = startOfDate(newEndDate, "day"); - newEndDate = addToDate(newEndDate, 1.5, "month"); - break; - case ViewMode.Day: - newStartDate = startOfDate(newStartDate, "day"); - newStartDate = addToDate(newStartDate, -1 * preStepsCount, "day"); - newEndDate = startOfDate(newEndDate, "day"); - newEndDate = addToDate(newEndDate, 19, "day"); - break; - case ViewMode.QuarterDay: - newStartDate = startOfDate(newStartDate, "day"); - newStartDate = addToDate(newStartDate, -1 * preStepsCount, "day"); - newEndDate = startOfDate(newEndDate, "day"); - newEndDate = addToDate(newEndDate, 66, "hour"); // 24(1 day)*3 - 6 - break; - case ViewMode.HalfDay: - newStartDate = startOfDate(newStartDate, "day"); - newStartDate = addToDate(newStartDate, -1 * preStepsCount, "day"); - newEndDate = startOfDate(newEndDate, "day"); - newEndDate = addToDate(newEndDate, 108, "hour"); // 24(1 day)*5 - 12 - break; - case ViewMode.Hour: - newStartDate = startOfDate(newStartDate, "hour"); - newStartDate = addToDate(newStartDate, -1 * preStepsCount, "hour"); - newEndDate = startOfDate(newEndDate, "day"); - newEndDate = addToDate(newEndDate, 1, "day"); - break; - } - return [newStartDate, newEndDate]; -}; - -export const seedDates = ( - startDate: Date, - endDate: Date, - viewMode: ViewMode -) => { - let currentDate: Date = new Date(startDate); - const dates: Date[] = [currentDate]; - while (currentDate < endDate) { - switch (viewMode) { - case ViewMode.Year: - currentDate = addToDate(currentDate, 1, "year"); - break; - case ViewMode.QuarterYear: - currentDate = addToDate(currentDate, 3, "month"); - break; - case ViewMode.Month: - currentDate = addToDate(currentDate, 1, "month"); - break; - case ViewMode.Week: - currentDate = addToDate(currentDate, 7, "day"); - break; - case ViewMode.Day: - currentDate = addToDate(currentDate, 1, "day"); - break; - case ViewMode.HalfDay: - currentDate = addToDate(currentDate, 12, "hour"); - break; - case ViewMode.QuarterDay: - currentDate = addToDate(currentDate, 6, "hour"); - break; - case ViewMode.Hour: - currentDate = addToDate(currentDate, 1, "hour"); - break; - } - dates.push(currentDate); - } - return dates; -}; - -export const getLocaleMonth = (date: Date, locale: string) => { - let bottomValue = getCachedDateTimeFormat(locale, { - month: "long", - }).format(date); - bottomValue = bottomValue.replace( - bottomValue[0], - bottomValue[0].toLocaleUpperCase() - ); - return bottomValue; -}; - -export const getLocalDayOfWeek = ( - date: Date, - locale: string, - format?: "long" | "short" | "narrow" | undefined -) => { - let bottomValue = getCachedDateTimeFormat(locale, { - weekday: format, - }).format(date); - bottomValue = bottomValue.replace( - bottomValue[0], - bottomValue[0].toLocaleUpperCase() - ); - return bottomValue; -}; - -/** - * Returns monday of current week - * @param date date for modify - */ -const getMonday = (date: Date) => { - const day = date.getDay(); - const diff = date.getDate() - day + (day === 0 ? -6 : 1); // adjust when day is sunday - return new Date(date.setDate(diff)); -}; - -export const getWeekNumberISO8601 = (date: Date) => { - const tmpDate = new Date(date.valueOf()); - const dayNumber = (tmpDate.getDay() + 6) % 7; - tmpDate.setDate(tmpDate.getDate() - dayNumber + 3); - const firstThursday = tmpDate.valueOf(); - tmpDate.setMonth(0, 1); - if (tmpDate.getDay() !== 4) { - tmpDate.setMonth(0, 1 + ((4 - tmpDate.getDay() + 7) % 7)); - } - const weekNumber = ( - 1 + Math.ceil((firstThursday - tmpDate.valueOf()) / 604800000) - ).toString(); - - if (weekNumber.length === 1) { - return `0${weekNumber}`; - } else { - return weekNumber; - } -}; - -export const getDaysInMonth = (month: number, year: number) => { - return new Date(year, month + 1, 0).getDate(); -}; diff --git a/application/frontend/src/helpers/other-helper.ts b/application/frontend/src/helpers/other-helper.ts deleted file mode 100644 index afd5a36..0000000 --- a/application/frontend/src/helpers/other-helper.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { BarTask } from "../types/bar-task"; -import { Task } from "../types/public-types"; - -export function isKeyboardEvent( - event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent -): event is React.KeyboardEvent { - return (event as React.KeyboardEvent).key !== undefined; -} - -export function isMouseEvent( - event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent -): event is React.MouseEvent { - return (event as React.MouseEvent).clientX !== undefined; -} - -export function isBarTask(task: Task | BarTask): task is BarTask { - return (task as BarTask).x1 !== undefined; -} - -export function removeHiddenTasks(tasks: Task[]) { - const groupedTasks = tasks.filter( - t => t.hideChildren && t.type === "project" - ); - if (groupedTasks.length > 0) { - for (let i = 0; groupedTasks.length > i; i++) { - const groupedTask = groupedTasks[i]; - const children = getChildren(tasks, groupedTask); - tasks = tasks.filter(t => children.indexOf(t) === -1); - } - } - return tasks; -} - -function getChildren(taskList: Task[], task: Task) { - let tasks: Task[] = []; - if (task.type !== "project") { - tasks = taskList.filter( - t => t.dependencies && t.dependencies.indexOf(task.id) !== -1 - ); - } else { - tasks = taskList.filter(t => t.project && t.project === task.id); - } - var taskChildren: Task[] = []; - tasks.forEach(t => { - taskChildren.push(...getChildren(taskList, t)); - }) - tasks = tasks.concat(tasks, taskChildren); - return tasks; -} - -export const sortTasks = (taskA: Task, taskB: Task) => { - const orderA = taskA.displayOrder || Number.MAX_VALUE; - const orderB = taskB.displayOrder || Number.MAX_VALUE; - if (orderA > orderB) { - return 1; - } else if (orderA < orderB) { - return -1; - } else { - return 0; - } -}; diff --git a/application/frontend/src/lib/calendar/calendar.module.css b/application/frontend/src/lib/calendar/calendar.module.css deleted file mode 100644 index 1be99d3..0000000 --- a/application/frontend/src/lib/calendar/calendar.module.css +++ /dev/null @@ -1,31 +0,0 @@ -.calendarBottomText { - text-anchor: middle; - fill: #333; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - pointer-events: none; -} - -.calendarTopTick { - stroke: #e6e4e4; -} - -.calendarTopText { - text-anchor: middle; - fill: #555; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - pointer-events: none; -} - -.calendarHeader { - fill: #ffffff; - stroke: #e0e0e0; - stroke-width: 1.4; -} diff --git a/application/frontend/src/lib/calendar/calendar.tsx b/application/frontend/src/lib/calendar/calendar.tsx deleted file mode 100644 index a5860db..0000000 --- a/application/frontend/src/lib/calendar/calendar.tsx +++ /dev/null @@ -1,395 +0,0 @@ -import React, { ReactChild } from "react"; -import { ViewMode } from "../../types/public-types"; -import { TopPartOfCalendar } from "./top-part-of-calendar"; -import { - getCachedDateTimeFormat, - getDaysInMonth, - getLocalDayOfWeek, - getLocaleMonth, - getWeekNumberISO8601, -} from "../../helpers/date-helper"; -import { DateSetup } from "../../types/date-setup"; -import styles from "./calendar.module.css"; - -export type CalendarProps = { - dateSetup: DateSetup; - locale: string; - viewMode: ViewMode; - rtl: boolean; - headerHeight: number; - columnWidth: number; - fontFamily: string; - fontSize: string; -}; - -export const Calendar: React.FC = ({ - dateSetup, - locale, - viewMode, - rtl, - headerHeight, - columnWidth, - fontFamily, - fontSize, -}) => { - const getCalendarValuesForYear = () => { - const topValues: ReactChild[] = []; - const bottomValues: ReactChild[] = []; - const topDefaultHeight = headerHeight * 0.5; - for (let i = 0; i < dateSetup.dates.length; i++) { - const date = dateSetup.dates[i]; - const bottomValue = date.getFullYear(); - bottomValues.push( - - {bottomValue} - - ); - if ( - i === 0 || - date.getFullYear() !== dateSetup.dates[i - 1].getFullYear() - ) { - const topValue = date.getFullYear().toString(); - let xText: number; - if (rtl) { - xText = (6 + i + date.getFullYear() + 1) * columnWidth; - } else { - xText = (6 + i - date.getFullYear()) * columnWidth; - } - topValues.push( - - ); - } - } - return [topValues, bottomValues]; - }; - - const getCalendarValuesForQuarterYear = () => { - const topValues: ReactChild[] = []; - const bottomValues: ReactChild[] = []; - const topDefaultHeight = headerHeight * 0.5; - for (let i = 0; i < dateSetup.dates.length; i++) { - const date = dateSetup.dates[i]; - // const bottomValue = getLocaleMonth(date, locale); - const quarter = "Q" + Math.floor((date.getMonth() + 3) / 3); - bottomValues.push( - - {quarter} - - ); - if ( - i === 0 || - date.getFullYear() !== dateSetup.dates[i - 1].getFullYear() - ) { - const topValue = date.getFullYear().toString(); - let xText: number; - if (rtl) { - xText = (6 + i + date.getMonth() + 1) * columnWidth; - } else { - xText = (6 + i - date.getMonth()) * columnWidth; - } - topValues.push( - - ); - } - } - return [topValues, bottomValues]; - }; - - const getCalendarValuesForMonth = () => { - const topValues: ReactChild[] = []; - const bottomValues: ReactChild[] = []; - const topDefaultHeight = headerHeight * 0.5; - for (let i = 0; i < dateSetup.dates.length; i++) { - const date = dateSetup.dates[i]; - const bottomValue = getLocaleMonth(date, locale); - bottomValues.push( - - {bottomValue} - - ); - if ( - i === 0 || - date.getFullYear() !== dateSetup.dates[i - 1].getFullYear() - ) { - const topValue = date.getFullYear().toString(); - let xText: number; - if (rtl) { - xText = (6 + i + date.getMonth() + 1) * columnWidth; - } else { - xText = (6 + i - date.getMonth()) * columnWidth; - } - topValues.push( - - ); - } - } - return [topValues, bottomValues]; - }; - - const getCalendarValuesForWeek = () => { - const topValues: ReactChild[] = []; - const bottomValues: ReactChild[] = []; - let weeksCount: number = 1; - const topDefaultHeight = headerHeight * 0.5; - const dates = dateSetup.dates; - for (let i = dates.length - 1; i >= 0; i--) { - const date = dates[i]; - let topValue = ""; - if (i === 0 || date.getMonth() !== dates[i - 1].getMonth()) { - // top - topValue = `${getLocaleMonth(date, locale)}, ${date.getFullYear()}`; - } - // bottom - const bottomValue = `W${getWeekNumberISO8601(date)}`; - - bottomValues.push( - - {bottomValue} - - ); - - if (topValue) { - // if last day is new month - if (i !== dates.length - 1) { - topValues.push( - - ); - } - weeksCount = 0; - } - weeksCount++; - } - return [topValues, bottomValues]; - }; - - const getCalendarValuesForDay = () => { - const topValues: ReactChild[] = []; - const bottomValues: ReactChild[] = []; - const topDefaultHeight = headerHeight * 0.5; - const dates = dateSetup.dates; - for (let i = 0; i < dates.length; i++) { - const date = dates[i]; - const bottomValue = `${getLocalDayOfWeek(date, locale, "short")}, ${date - .getDate() - .toString()}`; - - bottomValues.push( - - {bottomValue} - - ); - if ( - i + 1 !== dates.length && - date.getMonth() !== dates[i + 1].getMonth() - ) { - const topValue = getLocaleMonth(date, locale); - - topValues.push( - - ); - } - } - return [topValues, bottomValues]; - }; - - const getCalendarValuesForPartOfDay = () => { - const topValues: ReactChild[] = []; - const bottomValues: ReactChild[] = []; - const ticks = viewMode === ViewMode.HalfDay ? 2 : 4; - const topDefaultHeight = headerHeight * 0.5; - const dates = dateSetup.dates; - for (let i = 0; i < dates.length; i++) { - const date = dates[i]; - const bottomValue = getCachedDateTimeFormat(locale, { - hour: "numeric", - }).format(date); - - bottomValues.push( - - {bottomValue} - - ); - if (i === 0 || date.getDate() !== dates[i - 1].getDate()) { - const topValue = `${getLocalDayOfWeek( - date, - locale, - "short" - )}, ${date.getDate()} ${getLocaleMonth(date, locale)}`; - topValues.push( - - ); - } - } - - return [topValues, bottomValues]; - }; - - const getCalendarValuesForHour = () => { - const topValues: ReactChild[] = []; - const bottomValues: ReactChild[] = []; - const topDefaultHeight = headerHeight * 0.5; - const dates = dateSetup.dates; - for (let i = 0; i < dates.length; i++) { - const date = dates[i]; - const bottomValue = getCachedDateTimeFormat(locale, { - hour: "numeric", - }).format(date); - - bottomValues.push( - - {bottomValue} - - ); - if (i !== 0 && date.getDate() !== dates[i - 1].getDate()) { - const displayDate = dates[i - 1]; - const topValue = `${getLocalDayOfWeek( - displayDate, - locale, - "long" - )}, ${displayDate.getDate()} ${getLocaleMonth(displayDate, locale)}`; - const topPosition = (date.getHours() - 24) / 2; - topValues.push( - - ); - } - } - - return [topValues, bottomValues]; - }; - - let topValues: ReactChild[] = []; - let bottomValues: ReactChild[] = []; - switch (dateSetup.viewMode) { - case ViewMode.Year: - [topValues, bottomValues] = getCalendarValuesForYear(); - break; - case ViewMode.QuarterYear: - [topValues, bottomValues] = getCalendarValuesForQuarterYear(); - break; - case ViewMode.Month: - [topValues, bottomValues] = getCalendarValuesForMonth(); - break; - case ViewMode.Week: - [topValues, bottomValues] = getCalendarValuesForWeek(); - break; - case ViewMode.Day: - [topValues, bottomValues] = getCalendarValuesForDay(); - break; - case ViewMode.QuarterDay: - case ViewMode.HalfDay: - [topValues, bottomValues] = getCalendarValuesForPartOfDay(); - break; - case ViewMode.Hour: - [topValues, bottomValues] = getCalendarValuesForHour(); - } - return ( - - - {bottomValues} {topValues} - - ); -}; diff --git a/application/frontend/src/lib/calendar/top-part-of-calendar.tsx b/application/frontend/src/lib/calendar/top-part-of-calendar.tsx deleted file mode 100644 index d24f376..0000000 --- a/application/frontend/src/lib/calendar/top-part-of-calendar.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from "react"; -import styles from "./calendar.module.css"; - -type TopPartOfCalendarProps = { - value: string; - x1Line: number; - y1Line: number; - y2Line: number; - xText: number; - yText: number; -}; - -export const TopPartOfCalendar: React.FC = ({ - value, - x1Line, - y1Line, - y2Line, - xText, - yText, -}) => { - return ( - - - - {value} - - - ); -}; diff --git a/application/frontend/src/lib/gantt/gantt.module.css b/application/frontend/src/lib/gantt/gantt.module.css deleted file mode 100644 index 8169a19..0000000 --- a/application/frontend/src/lib/gantt/gantt.module.css +++ /dev/null @@ -1,21 +0,0 @@ -.ganttVerticalContainer { - overflow: hidden; - font-size: 0; - margin: 0; - padding: 0; -} - -.horizontalContainer { - margin: 0; - padding: 0; - overflow: hidden; -} - -.wrapper { - display: flex; - padding: 0; - margin: 0; - list-style: none; - outline: none; - position: relative; -} diff --git a/application/frontend/src/lib/gantt/gantt.tsx b/application/frontend/src/lib/gantt/gantt.tsx deleted file mode 100644 index 5aaaf65..0000000 --- a/application/frontend/src/lib/gantt/gantt.tsx +++ /dev/null @@ -1,474 +0,0 @@ -import React, { SyntheticEvent, useEffect, useMemo, useRef, useState } from "react" - -import { convertToBarTasks } from "../../helpers/bar-helper" -import { ganttDateRange, seedDates } from "../../helpers/date-helper" -import { removeHiddenTasks, sortTasks } from "../../helpers/other-helper" -import { BarTask } from "../../types/bar-task" -import { DateSetup } from "../../types/date-setup" -import { GanttEvent } from "../../types/gantt-task-actions" -import { GanttProps, Task, ViewMode } from "../../types/public-types" -import { CalendarProps } from "../calendar/calendar" -import { GridProps } from "../grid/grid" -import { HorizontalScroll } from "../other/horizontal-scroll" -import { StandardTooltipContent, Tooltip } from "../other/tooltip" -import { VerticalScroll } from "../other/vertical-scroll" -import { TaskList, TaskListProps } from "../task-list/task-list" -import { TaskListHeaderDefault } from "../task-list/task-list-header" -import { TaskListTableDefault } from "../task-list/task-list-table" -import styles from "./gantt.module.css" -import { TaskGantt } from "./task-gantt" -import { TaskGanttContentProps } from "./task-gantt-content" - -export const Gantt: React.FunctionComponent = ({ - tasks, - headerHeight = 50, - columnWidth = 60, - listCellWidth = "155px", - rowHeight = 50, - ganttHeight = 0, - viewMode = ViewMode.Day, - preStepsCount = 1, - locale = "en-GB", - barFill = 60, - barCornerRadius = 3, - barProgressColor = "#a3a3ff", - barProgressSelectedColor = "#8282f5", - barBackgroundColor = "#b8c2cc", - barBackgroundSelectedColor = "#aeb8c2", - projectProgressColor = "#7db59a", - projectProgressSelectedColor = "#59a985", - projectBackgroundColor = "#fac465", - projectBackgroundSelectedColor = "#f7bb53", - milestoneBackgroundColor = "#f1c453", - milestoneBackgroundSelectedColor = "#f29e4c", - rtl = false, - handleWidth = 8, - timeStep = 300000, - arrowColor = "grey", - fontFamily = "Arial, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue", - fontSize = "14px", - arrowIndent = 20, - todayColor = "rgba(252, 248, 227, 0.5)", - viewDate, - TooltipContent = StandardTooltipContent, - TaskListHeader = TaskListHeaderDefault, - TaskListTable = TaskListTableDefault, - onDateChange, - onProgressChange, - onDoubleClick, - onClick, - onDelete, - onSelect, - onExpanderClick, -}) => { - const wrapperRef = useRef(null) - const taskListRef = useRef(null) - const [dateSetup, setDateSetup] = useState(() => { - const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount) - return { viewMode, dates: seedDates(startDate, endDate, viewMode) } - }) - const [currentViewDate, setCurrentViewDate] = useState(undefined) - - const [taskListWidth, setTaskListWidth] = useState(0) - const [svgContainerWidth, setSvgContainerWidth] = useState(0) - const [svgContainerHeight, setSvgContainerHeight] = useState(ganttHeight) - const [barTasks, setBarTasks] = useState([]) - const [ganttEvent, setGanttEvent] = useState({ - action: "", - }) - const taskHeight = useMemo(() => (rowHeight * barFill) / 100, [rowHeight, barFill]) - - const [selectedTask, setSelectedTask] = useState() - const [failedTask, setFailedTask] = useState(null) - - const svgWidth = dateSetup.dates.length * columnWidth - const ganttFullHeight = barTasks.length * rowHeight - - const [scrollY, setScrollY] = useState(0) - const [scrollX, setScrollX] = useState(-1) - const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false) - - // task change events - useEffect(() => { - let filteredTasks: Task[] - if (onExpanderClick) { - filteredTasks = removeHiddenTasks(tasks) - } else { - filteredTasks = tasks - } - filteredTasks = filteredTasks.sort(sortTasks) - const [startDate, endDate] = ganttDateRange(filteredTasks, viewMode, preStepsCount) - let newDates = seedDates(startDate, endDate, viewMode) - if (rtl) { - newDates = newDates.reverse() - if (scrollX === -1) { - setScrollX(newDates.length * columnWidth) - } - } - setDateSetup({ dates: newDates, viewMode }) - setBarTasks( - convertToBarTasks( - filteredTasks, - newDates, - columnWidth, - rowHeight, - taskHeight, - barCornerRadius, - handleWidth, - rtl, - barProgressColor, - barProgressSelectedColor, - barBackgroundColor, - barBackgroundSelectedColor, - projectProgressColor, - projectProgressSelectedColor, - projectBackgroundColor, - projectBackgroundSelectedColor, - milestoneBackgroundColor, - milestoneBackgroundSelectedColor, - ), - ) - }, [ - tasks, - viewMode, - preStepsCount, - rowHeight, - barCornerRadius, - columnWidth, - taskHeight, - handleWidth, - barProgressColor, - barProgressSelectedColor, - barBackgroundColor, - barBackgroundSelectedColor, - projectProgressColor, - projectProgressSelectedColor, - projectBackgroundColor, - projectBackgroundSelectedColor, - milestoneBackgroundColor, - milestoneBackgroundSelectedColor, - rtl, - scrollX, - onExpanderClick, - ]) - - useEffect(() => { - if ( - viewMode === dateSetup.viewMode && - ((viewDate && !currentViewDate) || - (viewDate && currentViewDate?.valueOf() !== viewDate.valueOf())) - ) { - const dates = dateSetup.dates - const index = dates.findIndex( - (d, i) => - viewDate.valueOf() >= d.valueOf() && - i + 1 !== dates.length && - viewDate.valueOf() < dates[i + 1].valueOf(), - ) - if (index === -1) { - return - } - setCurrentViewDate(viewDate) - setScrollX(columnWidth * index) - } - }, [ - viewDate, - columnWidth, - dateSetup.dates, - dateSetup.viewMode, - viewMode, - currentViewDate, - setCurrentViewDate, - ]) - - useEffect(() => { - const { changedTask, action } = ganttEvent - if (changedTask) { - if (action === "delete") { - setGanttEvent({ action: "" }) - setBarTasks(barTasks.filter((t) => t.id !== changedTask.id)) - } else if ( - action === "move" || - action === "end" || - action === "start" || - action === "progress" - ) { - const prevStateTask = barTasks.find((t) => t.id === changedTask.id) - if ( - prevStateTask && - (prevStateTask.start.getTime() !== changedTask.start.getTime() || - prevStateTask.end.getTime() !== changedTask.end.getTime() || - prevStateTask.progress !== changedTask.progress) - ) { - // actions for change - const newTaskList = barTasks.map((t) => (t.id === changedTask.id ? changedTask : t)) - setBarTasks(newTaskList) - } - } - } - }, [ganttEvent, barTasks]) - - useEffect(() => { - if (failedTask) { - setBarTasks(barTasks.map((t) => (t.id !== failedTask.id ? t : failedTask))) - setFailedTask(null) - } - }, [failedTask, barTasks]) - - useEffect(() => { - if (!listCellWidth) { - setTaskListWidth(0) - } - if (taskListRef.current) { - setTaskListWidth(taskListRef.current.offsetWidth) - } - }, [taskListRef, listCellWidth]) - - useEffect(() => { - if (wrapperRef.current) { - setSvgContainerWidth(wrapperRef.current.offsetWidth - taskListWidth) - } - }, [wrapperRef, taskListWidth]) - - useEffect(() => { - if (ganttHeight) { - setSvgContainerHeight(ganttHeight + headerHeight) - } else { - setSvgContainerHeight(tasks.length * rowHeight + headerHeight) - } - }, [ganttHeight, tasks, headerHeight, rowHeight]) - - // scroll events - useEffect(() => { - const handleWheel = (event: WheelEvent) => { - if (event.shiftKey || event.deltaX) { - const scrollMove = event.deltaX ? event.deltaX : event.deltaY - let newScrollX = scrollX + scrollMove - if (newScrollX < 0) { - newScrollX = 0 - } else if (newScrollX > svgWidth) { - newScrollX = svgWidth - } - setScrollX(newScrollX) - event.preventDefault() - } else if (ganttHeight) { - let newScrollY = scrollY + event.deltaY - if (newScrollY < 0) { - newScrollY = 0 - } else if (newScrollY > ganttFullHeight - ganttHeight) { - newScrollY = ganttFullHeight - ganttHeight - } - if (newScrollY !== scrollY) { - setScrollY(newScrollY) - event.preventDefault() - } - } - - setIgnoreScrollEvent(true) - } - - // subscribe if scroll is necessary - wrapperRef.current?.addEventListener("wheel", handleWheel, { - passive: false, - }) - return () => { - wrapperRef.current?.removeEventListener("wheel", handleWheel) - } - }, [wrapperRef, scrollY, scrollX, ganttHeight, svgWidth, rtl, ganttFullHeight]) - - const handleScrollY = (event: SyntheticEvent) => { - if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) { - setScrollY(event.currentTarget.scrollTop) - setIgnoreScrollEvent(true) - } else { - setIgnoreScrollEvent(false) - } - } - - const handleScrollX = (event: SyntheticEvent) => { - if (scrollX !== event.currentTarget.scrollLeft && !ignoreScrollEvent) { - setScrollX(event.currentTarget.scrollLeft) - setIgnoreScrollEvent(true) - } else { - setIgnoreScrollEvent(false) - } - } - - /** - * Handles arrow keys events and transform it to new scroll - */ - const handleKeyDown = (event: React.KeyboardEvent) => { - event.preventDefault() - let newScrollY = scrollY - let newScrollX = scrollX - let isX = true - switch (event.key) { - case "Down": // IE/Edge specific value - case "ArrowDown": - newScrollY += rowHeight - isX = false - break - case "Up": // IE/Edge specific value - case "ArrowUp": - newScrollY -= rowHeight - isX = false - break - case "Left": - case "ArrowLeft": - newScrollX -= columnWidth - break - case "Right": // IE/Edge specific value - case "ArrowRight": - newScrollX += columnWidth - break - } - if (isX) { - if (newScrollX < 0) { - newScrollX = 0 - } else if (newScrollX > svgWidth) { - newScrollX = svgWidth - } - setScrollX(newScrollX) - } else { - if (newScrollY < 0) { - newScrollY = 0 - } else if (newScrollY > ganttFullHeight - ganttHeight) { - newScrollY = ganttFullHeight - ganttHeight - } - setScrollY(newScrollY) - } - setIgnoreScrollEvent(true) - } - - /** - * Task select event - */ - const handleSelectedTask = (taskId: string) => { - const newSelectedTask = barTasks.find((t) => t.id === taskId) - const oldSelectedTask = barTasks.find((t) => !!selectedTask && t.id === selectedTask.id) - if (onSelect) { - if (oldSelectedTask) { - onSelect(oldSelectedTask, false) - } - if (newSelectedTask) { - onSelect(newSelectedTask, true) - } - } - setSelectedTask(newSelectedTask) - } - const handleExpanderClick = (task: Task) => { - if (onExpanderClick && task.hideChildren !== undefined) { - onExpanderClick({ ...task, hideChildren: !task.hideChildren }) - } - } - const gridProps: GridProps = { - columnWidth, - svgWidth, - tasks: tasks, - rowHeight, - dates: dateSetup.dates, - todayColor, - rtl, - } - const calendarProps: CalendarProps = { - dateSetup, - locale, - viewMode, - headerHeight, - columnWidth, - fontFamily, - fontSize, - rtl, - } - const barProps: TaskGanttContentProps = { - tasks: barTasks, - dates: dateSetup.dates, - ganttEvent, - selectedTask, - rowHeight, - taskHeight, - columnWidth, - arrowColor, - timeStep, - fontFamily, - fontSize, - arrowIndent, - svgWidth, - rtl, - setGanttEvent, - setFailedTask, - setSelectedTask: handleSelectedTask, - onDateChange, - onProgressChange, - onDoubleClick, - onClick, - onDelete, - } - - const tableProps: TaskListProps = { - rowHeight, - rowWidth: listCellWidth, - fontFamily, - fontSize, - tasks: barTasks, - locale, - headerHeight, - scrollY, - ganttHeight, - horizontalContainerClass: styles.horizontalContainer, - selectedTask, - taskListRef, - setSelectedTask: handleSelectedTask, - onExpanderClick: handleExpanderClick, - TaskListHeader, - TaskListTable, - } - return ( -
-
- {listCellWidth && } - - {ganttEvent.changedTask && ( - - )} - -
- -
- ) -} diff --git a/application/frontend/src/lib/gantt/task-gantt-content.tsx b/application/frontend/src/lib/gantt/task-gantt-content.tsx deleted file mode 100644 index 33326df..0000000 --- a/application/frontend/src/lib/gantt/task-gantt-content.tsx +++ /dev/null @@ -1,302 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { EventOption } from "../../types/public-types"; -import { BarTask } from "../../types/bar-task"; -import { Arrow } from "../other/arrow"; -import { handleTaskBySVGMouseEvent } from "../../helpers/bar-helper"; -import { isKeyboardEvent } from "../../helpers/other-helper"; -import { TaskItem } from "../task-item/task-item"; -import { - BarMoveAction, - GanttContentMoveAction, - GanttEvent, -} from "../../types/gantt-task-actions"; - -export type TaskGanttContentProps = { - tasks: BarTask[]; - dates: Date[]; - ganttEvent: GanttEvent; - selectedTask: BarTask | undefined; - rowHeight: number; - columnWidth: number; - timeStep: number; - svg?: React.RefObject; - svgWidth: number; - taskHeight: number; - arrowColor: string; - arrowIndent: number; - fontSize: string; - fontFamily: string; - rtl: boolean; - setGanttEvent: (value: GanttEvent) => void; - setFailedTask: (value: BarTask | null) => void; - setSelectedTask: (taskId: string) => void; -} & EventOption; - -export const TaskGanttContent: React.FC = ({ - tasks, - dates, - ganttEvent, - selectedTask, - rowHeight, - columnWidth, - timeStep, - svg, - taskHeight, - arrowColor, - arrowIndent, - fontFamily, - fontSize, - rtl, - setGanttEvent, - setFailedTask, - setSelectedTask, - onDateChange, - onProgressChange, - onDoubleClick, - onClick, - onDelete, -}) => { - const point = svg?.current?.createSVGPoint(); - const [xStep, setXStep] = useState(0); - const [initEventX1Delta, setInitEventX1Delta] = useState(0); - const [isMoving, setIsMoving] = useState(false); - - // create xStep - useEffect(() => { - const dateDelta = - dates[1].getTime() - - dates[0].getTime() - - dates[1].getTimezoneOffset() * 60 * 1000 + - dates[0].getTimezoneOffset() * 60 * 1000; - const newXStep = (timeStep * columnWidth) / dateDelta; - setXStep(newXStep); - }, [columnWidth, dates, timeStep]); - - useEffect(() => { - const handleMouseMove = async (event: MouseEvent) => { - if (!ganttEvent.changedTask || !point || !svg?.current) return; - event.preventDefault(); - - point.x = event.clientX; - const cursor = point.matrixTransform( - svg?.current.getScreenCTM()?.inverse() - ); - - const { isChanged, changedTask } = handleTaskBySVGMouseEvent( - cursor.x, - ganttEvent.action as BarMoveAction, - ganttEvent.changedTask, - xStep, - timeStep, - initEventX1Delta, - rtl - ); - if (isChanged) { - setGanttEvent({ action: ganttEvent.action, changedTask }); - } - }; - - const handleMouseUp = async (event: MouseEvent) => { - const { action, originalSelectedTask, changedTask } = ganttEvent; - if (!changedTask || !point || !svg?.current || !originalSelectedTask) - return; - event.preventDefault(); - - point.x = event.clientX; - const cursor = point.matrixTransform( - svg?.current.getScreenCTM()?.inverse() - ); - const { changedTask: newChangedTask } = handleTaskBySVGMouseEvent( - cursor.x, - action as BarMoveAction, - changedTask, - xStep, - timeStep, - initEventX1Delta, - rtl - ); - - const isNotLikeOriginal = - originalSelectedTask.start !== newChangedTask.start || - originalSelectedTask.end !== newChangedTask.end || - originalSelectedTask.progress !== newChangedTask.progress; - - // remove listeners - svg.current.removeEventListener("mousemove", handleMouseMove); - svg.current.removeEventListener("mouseup", handleMouseUp); - setGanttEvent({ action: "" }); - setIsMoving(false); - - // custom operation start - let operationSuccess = true; - if ( - (action === "move" || action === "end" || action === "start") && - onDateChange && - isNotLikeOriginal - ) { - try { - const result = await onDateChange( - newChangedTask, - newChangedTask.barChildren - ); - if (result !== undefined) { - operationSuccess = result; - } - } catch (error) { - operationSuccess = false; - } - } else if (onProgressChange && isNotLikeOriginal) { - try { - const result = await onProgressChange( - newChangedTask, - newChangedTask.barChildren - ); - if (result !== undefined) { - operationSuccess = result; - } - } catch (error) { - operationSuccess = false; - } - } - - // If operation is failed - return old state - if (!operationSuccess) { - setFailedTask(originalSelectedTask); - } - }; - - if ( - !isMoving && - (ganttEvent.action === "move" || - ganttEvent.action === "end" || - ganttEvent.action === "start" || - ganttEvent.action === "progress") && - svg?.current - ) { - svg.current.addEventListener("mousemove", handleMouseMove); - svg.current.addEventListener("mouseup", handleMouseUp); - setIsMoving(true); - } - }, [ - ganttEvent, - xStep, - initEventX1Delta, - onProgressChange, - timeStep, - onDateChange, - svg, - isMoving, - point, - rtl, - setFailedTask, - setGanttEvent, - ]); - - /** - * Method is Start point of task change - */ - const handleBarEventStart = async ( - action: GanttContentMoveAction, - task: BarTask, - event?: React.MouseEvent | React.KeyboardEvent - ) => { - if (!event) { - if (action === "select") { - setSelectedTask(task.id); - } - } - // Keyboard events - else if (isKeyboardEvent(event)) { - if (action === "delete") { - if (onDelete) { - try { - const result = await onDelete(task); - if (result !== undefined && result) { - setGanttEvent({ action, changedTask: task }); - } - } catch (error) { - console.error("Error on Delete. " + error); - } - } - } - } - // Mouse Events - else if (action === "mouseenter") { - if (!ganttEvent.action) { - setGanttEvent({ - action, - changedTask: task, - originalSelectedTask: task, - }); - } - } else if (action === "mouseleave") { - if (ganttEvent.action === "mouseenter") { - setGanttEvent({ action: "" }); - } - } else if (action === "dblclick") { - !!onDoubleClick && onDoubleClick(task); - } else if (action === "click") { - !!onClick && onClick(task); - } - // Change task event start - else if (action === "move") { - if (!svg?.current || !point) return; - point.x = event.clientX; - const cursor = point.matrixTransform( - svg.current.getScreenCTM()?.inverse() - ); - setInitEventX1Delta(cursor.x - task.x1); - setGanttEvent({ - action, - changedTask: task, - originalSelectedTask: task, - }); - } else { - setGanttEvent({ - action, - changedTask: task, - originalSelectedTask: task, - }); - } - }; - - return ( - - - {tasks.map(task => { - return task.barChildren.map(child => { - return ( - - ); - }); - })} - - - {tasks.map(task => { - return ( - - ); - })} - - - ); -}; diff --git a/application/frontend/src/lib/gantt/task-gantt.tsx b/application/frontend/src/lib/gantt/task-gantt.tsx deleted file mode 100644 index 73a7668..0000000 --- a/application/frontend/src/lib/gantt/task-gantt.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { useRef, useEffect } from "react"; -import { GridProps, Grid } from "../grid/grid"; -import { CalendarProps, Calendar } from "../calendar/calendar"; -import { TaskGanttContentProps, TaskGanttContent } from "./task-gantt-content"; -import styles from "./gantt.module.css"; - -export type TaskGanttProps = { - gridProps: GridProps; - calendarProps: CalendarProps; - barProps: TaskGanttContentProps; - ganttHeight: number; - scrollY: number; - scrollX: number; -}; -export const TaskGantt: React.FC = ({ - gridProps, - calendarProps, - barProps, - ganttHeight, - scrollY, - scrollX, -}) => { - const ganttSVGRef = useRef(null); - const horizontalContainerRef = useRef(null); - const verticalGanttContainerRef = useRef(null); - const newBarProps = { ...barProps, svg: ganttSVGRef }; - - useEffect(() => { - if (horizontalContainerRef.current) { - horizontalContainerRef.current.scrollTop = scrollY; - } - }, [scrollY]); - - useEffect(() => { - if (verticalGanttContainerRef.current) { - verticalGanttContainerRef.current.scrollLeft = scrollX; - } - }, [scrollX]); - - return ( -
- - - -
- - - - -
-
- ); -}; diff --git a/application/frontend/src/lib/grid/grid-body.tsx b/application/frontend/src/lib/grid/grid-body.tsx deleted file mode 100644 index 18e6f2b..0000000 --- a/application/frontend/src/lib/grid/grid-body.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { ReactChild } from "react"; -import { Task } from "../../types/public-types"; -import { addToDate } from "../../helpers/date-helper"; -import styles from "./grid.module.css"; - -export type GridBodyProps = { - tasks: Task[]; - dates: Date[]; - svgWidth: number; - rowHeight: number; - columnWidth: number; - todayColor: string; - rtl: boolean; -}; -export const GridBody: React.FC = ({ - tasks, - dates, - rowHeight, - svgWidth, - columnWidth, - todayColor, - rtl, -}) => { - let y = 0; - const gridRows: ReactChild[] = []; - const rowLines: ReactChild[] = [ - , - ]; - for (const task of tasks) { - gridRows.push( - - ); - rowLines.push( - - ); - y += rowHeight; - } - - const now = new Date(); - let tickX = 0; - const ticks: ReactChild[] = []; - let today: ReactChild = ; - for (let i = 0; i < dates.length; i++) { - const date = dates[i]; - ticks.push( - - ); - if ( - (i + 1 !== dates.length && - date.getTime() < now.getTime() && - dates[i + 1].getTime() >= now.getTime()) || - // if current date is last - (i !== 0 && - i + 1 === dates.length && - date.getTime() < now.getTime() && - addToDate( - date, - date.getTime() - dates[i - 1].getTime(), - "millisecond" - ).getTime() >= now.getTime()) - ) { - today = ( - - ); - } - // rtl for today - if ( - rtl && - i + 1 !== dates.length && - date.getTime() >= now.getTime() && - dates[i + 1].getTime() < now.getTime() - ) { - today = ( - - ); - } - tickX += columnWidth; - } - return ( - - {gridRows} - {rowLines} - {ticks} - {today} - - ); -}; diff --git a/application/frontend/src/lib/grid/grid.module.css b/application/frontend/src/lib/grid/grid.module.css deleted file mode 100644 index 964303f..0000000 --- a/application/frontend/src/lib/grid/grid.module.css +++ /dev/null @@ -1,15 +0,0 @@ -.gridRow { - fill: #fff; -} - -.gridRow:nth-child(even) { - fill: #f5f5f5; -} - -.gridRowLine { - stroke: #ebeff2; -} - -.gridTick { - stroke: #e6e4e4; -} diff --git a/application/frontend/src/lib/grid/grid.tsx b/application/frontend/src/lib/grid/grid.tsx deleted file mode 100644 index 488cfa3..0000000 --- a/application/frontend/src/lib/grid/grid.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; -import { GridBody, GridBodyProps } from "./grid-body"; - -export type GridProps = GridBodyProps; -export const Grid: React.FC = props => { - return ( - - - - ); -}; diff --git a/application/frontend/src/lib/other/arrow.tsx b/application/frontend/src/lib/other/arrow.tsx deleted file mode 100644 index 52e8f28..0000000 --- a/application/frontend/src/lib/other/arrow.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React from "react"; -import { BarTask } from "../../types/bar-task"; - -type ArrowProps = { - taskFrom: BarTask; - taskTo: BarTask; - rowHeight: number; - taskHeight: number; - arrowIndent: number; - rtl: boolean; -}; -export const Arrow: React.FC = ({ - taskFrom, - taskTo, - rowHeight, - taskHeight, - arrowIndent, - rtl, -}) => { - let path: string; - let trianglePoints: string; - if (rtl) { - [path, trianglePoints] = drownPathAndTriangleRTL( - taskFrom, - taskTo, - rowHeight, - taskHeight, - arrowIndent - ); - } else { - [path, trianglePoints] = drownPathAndTriangle( - taskFrom, - taskTo, - rowHeight, - taskHeight, - arrowIndent - ); - } - - return ( - - - - - ); -}; - -const drownPathAndTriangle = ( - taskFrom: BarTask, - taskTo: BarTask, - rowHeight: number, - taskHeight: number, - arrowIndent: number -) => { - const indexCompare = taskFrom.index > taskTo.index ? -1 : 1; - const taskToEndPosition = taskTo.y + taskHeight / 2; - const taskFromEndPosition = taskFrom.x2 + arrowIndent * 2; - const taskFromHorizontalOffsetValue = - taskFromEndPosition < taskTo.x1 ? "" : `H ${taskTo.x1 - arrowIndent}`; - const taskToHorizontalOffsetValue = - taskFromEndPosition > taskTo.x1 - ? arrowIndent - : taskTo.x1 - taskFrom.x2 - arrowIndent; - - const path = `M ${taskFrom.x2} ${taskFrom.y + taskHeight / 2} - h ${arrowIndent} - v ${(indexCompare * rowHeight) / 2} - ${taskFromHorizontalOffsetValue} - V ${taskToEndPosition} - h ${taskToHorizontalOffsetValue}`; - - const trianglePoints = `${taskTo.x1},${taskToEndPosition} - ${taskTo.x1 - 5},${taskToEndPosition - 5} - ${taskTo.x1 - 5},${taskToEndPosition + 5}`; - return [path, trianglePoints]; -}; - -const drownPathAndTriangleRTL = ( - taskFrom: BarTask, - taskTo: BarTask, - rowHeight: number, - taskHeight: number, - arrowIndent: number -) => { - const indexCompare = taskFrom.index > taskTo.index ? -1 : 1; - const taskToEndPosition = taskTo.y + taskHeight / 2; - const taskFromEndPosition = taskFrom.x1 - arrowIndent * 2; - const taskFromHorizontalOffsetValue = - taskFromEndPosition > taskTo.x2 ? "" : `H ${taskTo.x2 + arrowIndent}`; - const taskToHorizontalOffsetValue = - taskFromEndPosition < taskTo.x2 - ? -arrowIndent - : taskTo.x2 - taskFrom.x1 + arrowIndent; - - const path = `M ${taskFrom.x1} ${taskFrom.y + taskHeight / 2} - h ${-arrowIndent} - v ${(indexCompare * rowHeight) / 2} - ${taskFromHorizontalOffsetValue} - V ${taskToEndPosition} - h ${taskToHorizontalOffsetValue}`; - - const trianglePoints = `${taskTo.x2},${taskToEndPosition} - ${taskTo.x2 + 5},${taskToEndPosition + 5} - ${taskTo.x2 + 5},${taskToEndPosition - 5}`; - return [path, trianglePoints]; -}; diff --git a/application/frontend/src/lib/other/horizontal-scroll.module.css b/application/frontend/src/lib/other/horizontal-scroll.module.css deleted file mode 100644 index dcf787e..0000000 --- a/application/frontend/src/lib/other/horizontal-scroll.module.css +++ /dev/null @@ -1,33 +0,0 @@ -.scrollWrapper { - overflow: auto; - max-width: 100%; - /*firefox*/ - scrollbar-width: thin; - /*iPad*/ - height: 1.2rem; -} -.scrollWrapper::-webkit-scrollbar { - width: 1.1rem; - height: 1.1rem; -} -.scrollWrapper::-webkit-scrollbar-corner { - background: transparent; -} -.scrollWrapper::-webkit-scrollbar-thumb { - border: 6px solid transparent; - background: rgba(0, 0, 0, 0.2); - background: var(--palette-black-alpha-20, rgba(0, 0, 0, 0.2)); - border-radius: 10px; - background-clip: padding-box; -} -.scrollWrapper::-webkit-scrollbar-thumb:hover { - border: 4px solid transparent; - background: rgba(0, 0, 0, 0.3); - background: var(--palette-black-alpha-30, rgba(0, 0, 0, 0.3)); - background-clip: padding-box; -} -@media only screen and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) { -} -.scroll { - height: 1px; -} diff --git a/application/frontend/src/lib/other/horizontal-scroll.tsx b/application/frontend/src/lib/other/horizontal-scroll.tsx deleted file mode 100644 index 5426460..0000000 --- a/application/frontend/src/lib/other/horizontal-scroll.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { SyntheticEvent, useRef, useEffect } from "react"; -import styles from "./horizontal-scroll.module.css"; - -export const HorizontalScroll: React.FC<{ - scroll: number; - svgWidth: number; - taskListWidth: number; - rtl: boolean; - onScroll: (event: SyntheticEvent) => void; -}> = ({ scroll, svgWidth, taskListWidth, rtl, onScroll }) => { - const scrollRef = useRef(null); - - useEffect(() => { - if (scrollRef.current) { - scrollRef.current.scrollLeft = scroll; - } - }, [scroll]); - - return ( -
-
-
- ); -}; diff --git a/application/frontend/src/lib/other/tooltip.module.css b/application/frontend/src/lib/other/tooltip.module.css deleted file mode 100644 index d5793ef..0000000 --- a/application/frontend/src/lib/other/tooltip.module.css +++ /dev/null @@ -1,30 +0,0 @@ -.tooltipDefaultContainer { - background: #fff; - padding: 12px; - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); -} - -.tooltipDefaultContainerParagraph { - font-size: 12px; - margin-bottom: 6px; - color: #666; -} - -.tooltipDetailsContainer { - position: absolute; - display: flex; - flex-shrink: 0; - pointer-events: none; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.tooltipDetailsContainerHidden { - visibility: hidden; - position: absolute; - display: flex; - pointer-events: none; -} diff --git a/application/frontend/src/lib/other/tooltip.tsx b/application/frontend/src/lib/other/tooltip.tsx deleted file mode 100644 index 7542a14..0000000 --- a/application/frontend/src/lib/other/tooltip.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useRef, useEffect, useState } from "react"; -import { Task } from "../../types/public-types"; -import { BarTask } from "../../types/bar-task"; -import styles from "./tooltip.module.css"; - -export type TooltipProps = { - task: BarTask; - arrowIndent: number; - rtl: boolean; - svgContainerHeight: number; - svgContainerWidth: number; - svgWidth: number; - headerHeight: number; - taskListWidth: number; - scrollX: number; - scrollY: number; - rowHeight: number; - fontSize: string; - fontFamily: string; - TooltipContent: React.FC<{ - task: Task; - fontSize: string; - fontFamily: string; - }>; -}; -export const Tooltip: React.FC = ({ - task, - rowHeight, - rtl, - svgContainerHeight, - svgContainerWidth, - scrollX, - scrollY, - arrowIndent, - fontSize, - fontFamily, - headerHeight, - taskListWidth, - TooltipContent, -}) => { - const tooltipRef = useRef(null); - const [relatedY, setRelatedY] = useState(0); - const [relatedX, setRelatedX] = useState(0); - useEffect(() => { - if (tooltipRef.current) { - const tooltipHeight = tooltipRef.current.offsetHeight * 1.1; - const tooltipWidth = tooltipRef.current.offsetWidth * 1.1; - - let newRelatedY = task.index * rowHeight - scrollY + headerHeight; - let newRelatedX: number; - if (rtl) { - newRelatedX = task.x1 - arrowIndent * 1.5 - tooltipWidth - scrollX; - if (newRelatedX < 0) { - newRelatedX = task.x2 + arrowIndent * 1.5 - scrollX; - } - const tooltipLeftmostPoint = tooltipWidth + newRelatedX; - if (tooltipLeftmostPoint > svgContainerWidth) { - newRelatedX = svgContainerWidth - tooltipWidth; - newRelatedY += rowHeight; - } - } else { - newRelatedX = task.x2 + arrowIndent * 1.5 + taskListWidth - scrollX; - const tooltipLeftmostPoint = tooltipWidth + newRelatedX; - const fullChartWidth = taskListWidth + svgContainerWidth; - if (tooltipLeftmostPoint > fullChartWidth) { - newRelatedX = - task.x1 + - taskListWidth - - arrowIndent * 1.5 - - scrollX - - tooltipWidth; - } - if (newRelatedX < taskListWidth) { - newRelatedX = svgContainerWidth + taskListWidth - tooltipWidth; - newRelatedY += rowHeight; - } - } - - const tooltipLowerPoint = tooltipHeight + newRelatedY - scrollY; - if (tooltipLowerPoint > svgContainerHeight - scrollY) { - newRelatedY = svgContainerHeight - tooltipHeight; - } - setRelatedY(newRelatedY); - setRelatedX(newRelatedX); - } - }, [ - tooltipRef, - task, - arrowIndent, - scrollX, - scrollY, - headerHeight, - taskListWidth, - rowHeight, - svgContainerHeight, - svgContainerWidth, - rtl, - ]); - - return ( -
- -
- ); -}; - -export const StandardTooltipContent: React.FC<{ - task: Task; - fontSize: string; - fontFamily: string; -}> = ({ task, fontSize, fontFamily }) => { - const style = { - fontSize, - fontFamily, - }; - return ( -
- {`${ - task.name - }: ${task.start.getDate()}-${ - task.start.getMonth() + 1 - }-${task.start.getFullYear()} - ${task.end.getDate()}-${ - task.end.getMonth() + 1 - }-${task.end.getFullYear()}`} - {task.end.getTime() - task.start.getTime() !== 0 && ( -

{`Duration: ${~~( - (task.end.getTime() - task.start.getTime()) / - (1000 * 60 * 60 * 24) - )} day(s)`}

- )} - -

- {!!task.progress && `Progress: ${task.progress} %`} -

-
- ); -}; diff --git a/application/frontend/src/lib/other/vertical-scroll.module.css b/application/frontend/src/lib/other/vertical-scroll.module.css deleted file mode 100644 index da55a2e..0000000 --- a/application/frontend/src/lib/other/vertical-scroll.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.scroll { - overflow: hidden auto; - width: 1rem; - flex-shrink: 0; - /*firefox*/ - scrollbar-width: thin; -} -.scroll::-webkit-scrollbar { - width: 1.1rem; - height: 1.1rem; -} -.scroll::-webkit-scrollbar-corner { - background: transparent; -} -.scroll::-webkit-scrollbar-thumb { - border: 6px solid transparent; - background: rgba(0, 0, 0, 0.2); - background: var(--palette-black-alpha-20, rgba(0, 0, 0, 0.2)); - border-radius: 10px; - background-clip: padding-box; -} -.scroll::-webkit-scrollbar-thumb:hover { - border: 4px solid transparent; - background: rgba(0, 0, 0, 0.3); - background: var(--palette-black-alpha-30, rgba(0, 0, 0, 0.3)); - background-clip: padding-box; -} diff --git a/application/frontend/src/lib/other/vertical-scroll.tsx b/application/frontend/src/lib/other/vertical-scroll.tsx deleted file mode 100644 index d01d46e..0000000 --- a/application/frontend/src/lib/other/vertical-scroll.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { SyntheticEvent, useRef, useEffect } from "react"; -import styles from "./vertical-scroll.module.css"; - -export const VerticalScroll: React.FC<{ - scroll: number; - ganttHeight: number; - ganttFullHeight: number; - headerHeight: number; - rtl: boolean; - onScroll: (event: SyntheticEvent) => void; -}> = ({ - scroll, - ganttHeight, - ganttFullHeight, - headerHeight, - rtl, - onScroll, -}) => { - const scrollRef = useRef(null); - - useEffect(() => { - if (scrollRef.current) { - scrollRef.current.scrollTop = scroll; - } - }, [scroll]); - - return ( -
-
-
- ); -}; diff --git a/application/frontend/src/lib/task-item/bar/bar-date-handle.tsx b/application/frontend/src/lib/task-item/bar/bar-date-handle.tsx deleted file mode 100644 index 3794239..0000000 --- a/application/frontend/src/lib/task-item/bar/bar-date-handle.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react"; -import styles from "./bar.module.css"; - -type BarDateHandleProps = { - x: number; - y: number; - width: number; - height: number; - barCornerRadius: number; - onMouseDown: (event: React.MouseEvent) => void; -}; -export const BarDateHandle: React.FC = ({ - x, - y, - width, - height, - barCornerRadius, - onMouseDown, -}) => { - return ( - - ); -}; diff --git a/application/frontend/src/lib/task-item/bar/bar-display.tsx b/application/frontend/src/lib/task-item/bar/bar-display.tsx deleted file mode 100644 index 174e1ed..0000000 --- a/application/frontend/src/lib/task-item/bar/bar-display.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react"; -import style from "./bar.module.css"; - -type BarDisplayProps = { - x: number; - y: number; - width: number; - height: number; - isSelected: boolean; - /* progress start point */ - progressX: number; - progressWidth: number; - barCornerRadius: number; - styles: { - backgroundColor: string; - backgroundSelectedColor: string; - progressColor: string; - progressSelectedColor: string; - }; - onMouseDown: (event: React.MouseEvent) => void; -}; -export const BarDisplay: React.FC = ({ - x, - y, - width, - height, - isSelected, - progressX, - progressWidth, - barCornerRadius, - styles, - onMouseDown, -}) => { - const getProcessColor = () => { - return isSelected ? styles.progressSelectedColor : styles.progressColor; - }; - - const getBarColor = () => { - return isSelected ? styles.backgroundSelectedColor : styles.backgroundColor; - }; - - return ( - - - - - ); -}; diff --git a/application/frontend/src/lib/task-item/bar/bar-progress-handle.tsx b/application/frontend/src/lib/task-item/bar/bar-progress-handle.tsx deleted file mode 100644 index 75168b4..0000000 --- a/application/frontend/src/lib/task-item/bar/bar-progress-handle.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import styles from "./bar.module.css"; - -type BarProgressHandleProps = { - progressPoint: string; - onMouseDown: (event: React.MouseEvent) => void; -}; -export const BarProgressHandle: React.FC = ({ - progressPoint, - onMouseDown, -}) => { - return ( - - ); -}; diff --git a/application/frontend/src/lib/task-item/bar/bar-small.tsx b/application/frontend/src/lib/task-item/bar/bar-small.tsx deleted file mode 100644 index 56f4343..0000000 --- a/application/frontend/src/lib/task-item/bar/bar-small.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from "react"; -import { getProgressPoint } from "../../../helpers/bar-helper"; -import { BarDisplay } from "./bar-display"; -import { BarProgressHandle } from "./bar-progress-handle"; -import { TaskItemProps } from "../task-item"; -import styles from "./bar.module.css"; - -export const BarSmall: React.FC = ({ - task, - isProgressChangeable, - isDateChangeable, - onEventStart, - isSelected, -}) => { - const progressPoint = getProgressPoint( - task.progressWidth + task.x1, - task.y, - task.height - ); - return ( - - { - isDateChangeable && onEventStart("move", task, e); - }} - /> - - {isProgressChangeable && ( - { - onEventStart("progress", task, e); - }} - /> - )} - - - ); -}; diff --git a/application/frontend/src/lib/task-item/bar/bar.module.css b/application/frontend/src/lib/task-item/bar/bar.module.css deleted file mode 100644 index 7ff4926..0000000 --- a/application/frontend/src/lib/task-item/bar/bar.module.css +++ /dev/null @@ -1,21 +0,0 @@ -.barWrapper { - cursor: pointer; - outline: none; -} - -.barWrapper:hover .barHandle { - visibility: visible; - opacity: 1; -} - -.barHandle { - fill: #ddd; - cursor: ew-resize; - opacity: 0; - visibility: hidden; -} - -.barBackground { - user-select: none; - stroke-width: 0; -} diff --git a/application/frontend/src/lib/task-item/bar/bar.tsx b/application/frontend/src/lib/task-item/bar/bar.tsx deleted file mode 100644 index 7e6ce5b..0000000 --- a/application/frontend/src/lib/task-item/bar/bar.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from "react"; -import { getProgressPoint } from "../../../helpers/bar-helper"; -import { BarDisplay } from "./bar-display"; -import { BarDateHandle } from "./bar-date-handle"; -import { BarProgressHandle } from "./bar-progress-handle"; -import { TaskItemProps } from "../task-item"; -import styles from "./bar.module.css"; - -export const Bar: React.FC = ({ - task, - isProgressChangeable, - isDateChangeable, - rtl, - onEventStart, - isSelected, -}) => { - const progressPoint = getProgressPoint( - +!rtl * task.progressWidth + task.progressX, - task.y, - task.height - ); - const handleHeight = task.height - 2; - return ( - - { - isDateChangeable && onEventStart("move", task, e); - }} - /> - - {isDateChangeable && ( - - {/* left */} - { - onEventStart("start", task, e); - }} - /> - {/* right */} - { - onEventStart("end", task, e); - }} - /> - - )} - {isProgressChangeable && ( - { - onEventStart("progress", task, e); - }} - /> - )} - - - ); -}; diff --git a/application/frontend/src/lib/task-item/milestone/milestone.module.css b/application/frontend/src/lib/task-item/milestone/milestone.module.css deleted file mode 100644 index f8766ba..0000000 --- a/application/frontend/src/lib/task-item/milestone/milestone.module.css +++ /dev/null @@ -1,8 +0,0 @@ -.milestoneWrapper { - cursor: pointer; - outline: none; -} - -.milestoneBackground { - user-select: none; -} diff --git a/application/frontend/src/lib/task-item/milestone/milestone.tsx b/application/frontend/src/lib/task-item/milestone/milestone.tsx deleted file mode 100644 index a8e3922..0000000 --- a/application/frontend/src/lib/task-item/milestone/milestone.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; -import { TaskItemProps } from "../task-item"; -import styles from "./milestone.module.css"; - -export const Milestone: React.FC = ({ - task, - isDateChangeable, - onEventStart, - isSelected, -}) => { - const transform = `rotate(45 ${task.x1 + task.height * 0.356} - ${task.y + task.height * 0.85})`; - const getBarColor = () => { - return isSelected - ? task.styles.backgroundSelectedColor - : task.styles.backgroundColor; - }; - - return ( - - { - isDateChangeable && onEventStart("move", task, e); - }} - /> - - ); -}; diff --git a/application/frontend/src/lib/task-item/project/project.module.css b/application/frontend/src/lib/task-item/project/project.module.css deleted file mode 100644 index 4fa67c2..0000000 --- a/application/frontend/src/lib/task-item/project/project.module.css +++ /dev/null @@ -1,13 +0,0 @@ -.projectWrapper { - cursor: pointer; - outline: none; -} - -.projectBackground { - user-select: none; - opacity: 0.6; -} - -.projectTop { - user-select: none; -} diff --git a/application/frontend/src/lib/task-item/project/project.tsx b/application/frontend/src/lib/task-item/project/project.tsx deleted file mode 100644 index 5a47ba9..0000000 --- a/application/frontend/src/lib/task-item/project/project.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from "react"; -import { TaskItemProps } from "../task-item"; -import styles from "./project.module.css"; - -export const Project: React.FC = ({ task, isSelected }) => { - const barColor = isSelected - ? task.styles.backgroundSelectedColor - : task.styles.backgroundColor; - const processColor = isSelected - ? task.styles.progressSelectedColor - : task.styles.progressColor; - const projectWith = task.x2 - task.x1; - - const projectLeftTriangle = [ - task.x1, - task.y + task.height / 2 - 1, - task.x1, - task.y + task.height, - task.x1 + 15, - task.y + task.height / 2 - 1, - ].join(","); - const projectRightTriangle = [ - task.x2, - task.y + task.height / 2 - 1, - task.x2, - task.y + task.height, - task.x2 - 15, - task.y + task.height / 2 - 1, - ].join(","); - - return ( - - - - - - - - ); -}; diff --git a/application/frontend/src/lib/task-item/task-item.tsx b/application/frontend/src/lib/task-item/task-item.tsx deleted file mode 100644 index cf06284..0000000 --- a/application/frontend/src/lib/task-item/task-item.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import { BarTask } from "../../types/bar-task"; -import { GanttContentMoveAction } from "../../types/gantt-task-actions"; -import { Bar } from "./bar/bar"; -import { BarSmall } from "./bar/bar-small"; -import { Milestone } from "./milestone/milestone"; -import { Project } from "./project/project"; -import style from "./task-list.module.css"; - -export type TaskItemProps = { - task: BarTask; - arrowIndent: number; - taskHeight: number; - isProgressChangeable: boolean; - isDateChangeable: boolean; - isDelete: boolean; - isSelected: boolean; - rtl: boolean; - onEventStart: ( - action: GanttContentMoveAction, - selectedTask: BarTask, - event?: React.MouseEvent | React.KeyboardEvent - ) => any; -}; - -export const TaskItem: React.FC = props => { - const { - task, - arrowIndent, - isDelete, - taskHeight, - isSelected, - rtl, - onEventStart, - } = { - ...props, - }; - const textRef = useRef(null); - const [taskItem, setTaskItem] = useState(
); - const [isTextInside, setIsTextInside] = useState(true); - - useEffect(() => { - switch (task.typeInternal) { - case "milestone": - setTaskItem(); - break; - case "project": - setTaskItem(); - break; - case "smalltask": - setTaskItem(); - break; - default: - setTaskItem(); - break; - } - }, [task, isSelected]); - - useEffect(() => { - if (textRef.current) { - setIsTextInside(textRef.current.getBBox().width < task.x2 - task.x1); - } - }, [textRef, task]); - - const getX = () => { - const width = task.x2 - task.x1; - const hasChild = task.barChildren.length > 0; - if (isTextInside) { - return task.x1 + width * 0.5; - } - if (rtl && textRef.current) { - return ( - task.x1 - - textRef.current.getBBox().width - - arrowIndent * +hasChild - - arrowIndent * 0.2 - ); - } else { - return task.x1 + width + arrowIndent * +hasChild + arrowIndent * 0.2; - } - }; - - return ( - { - switch (e.key) { - case "Delete": { - if (isDelete) onEventStart("delete", task, e); - break; - } - } - e.stopPropagation(); - }} - onMouseEnter={e => { - onEventStart("mouseenter", task, e); - }} - onMouseLeave={e => { - onEventStart("mouseleave", task, e); - }} - onDoubleClick={e => { - onEventStart("dblclick", task, e); - }} - onClick={e => { - onEventStart("click", task, e); - }} - onFocus={() => { - onEventStart("select", task); - }} - > - {taskItem} - - {task.name} - - - ); -}; diff --git a/application/frontend/src/lib/task-item/task-list.module.css b/application/frontend/src/lib/task-item/task-list.module.css deleted file mode 100644 index 2eec420..0000000 --- a/application/frontend/src/lib/task-item/task-list.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.barLabel { - fill: #fff; - text-anchor: middle; - font-weight: lighter; - dominant-baseline: central; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - pointer-events: none; -} - -.barLabelOutside { - fill: #555; - text-anchor: start; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - pointer-events: none; -} diff --git a/application/frontend/src/lib/task-list/task-list-header.module.css b/application/frontend/src/lib/task-list/task-list-header.module.css deleted file mode 100644 index c250354..0000000 --- a/application/frontend/src/lib/task-list/task-list-header.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.ganttTable { - display: table; - border-bottom: #e6e4e4 1px solid; - border-top: #e6e4e4 1px solid; - border-left: #e6e4e4 1px solid; -} - -.ganttTable_Header { - display: table-row; - list-style: none; -} - -.ganttTable_HeaderSeparator { - border-right: 1px solid rgb(196, 196, 196); - opacity: 1; - margin-left: -2px; -} - -.ganttTable_HeaderItem { - display: table-cell; - vertical-align: -webkit-baseline-middle; - vertical-align: middle; -} diff --git a/application/frontend/src/lib/task-list/task-list-header.tsx b/application/frontend/src/lib/task-list/task-list-header.tsx deleted file mode 100644 index 4e8cdb6..0000000 --- a/application/frontend/src/lib/task-list/task-list-header.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react"; -import styles from "./task-list-header.module.css"; - -export const TaskListHeaderDefault: React.FC<{ - headerHeight: number; - rowWidth: string; - fontFamily: string; - fontSize: string; -}> = ({ headerHeight, fontFamily, fontSize, rowWidth }) => { - return ( -
-
-
-  Name -
-
-
-  From -
-
-
-  To -
-
-
- ); -}; diff --git a/application/frontend/src/lib/task-list/task-list-table.module.css b/application/frontend/src/lib/task-list/task-list-table.module.css deleted file mode 100644 index 7f57268..0000000 --- a/application/frontend/src/lib/task-list/task-list-table.module.css +++ /dev/null @@ -1,38 +0,0 @@ -.taskListWrapper { - display: table; - border-bottom: #e6e4e4 1px solid; - border-left: #e6e4e4 1px solid; -} - -.taskListTableRow { - display: table-row; - text-overflow: ellipsis; -} - -.taskListTableRow:nth-of-type(even) { - background-color: #f5f5f5; -} - -.taskListCell { - display: table-cell; - vertical-align: middle; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.taskListNameWrapper { - display: flex; -} - -.taskListExpander { - color: rgb(86 86 86); - font-size: 0.6rem; - padding: 0.15rem 0.2rem 0rem 0.2rem; - user-select: none; - cursor: pointer; -} -.taskListEmptyExpander { - font-size: 0.6rem; - padding-left: 1rem; - user-select: none; -} diff --git a/application/frontend/src/lib/task-list/task-list-table.tsx b/application/frontend/src/lib/task-list/task-list-table.tsx deleted file mode 100644 index b165f60..0000000 --- a/application/frontend/src/lib/task-list/task-list-table.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useMemo } from "react"; -import styles from "./task-list-table.module.css"; -import { Task } from "../../types/public-types"; - -const localeDateStringCache = {}; -const toLocaleDateStringFactory = - (locale: string) => - (date: Date, dateTimeOptions: Intl.DateTimeFormatOptions) => { - const key = date.toString(); - let lds = localeDateStringCache[key]; - if (!lds) { - lds = date.toLocaleDateString(locale, dateTimeOptions); - localeDateStringCache[key] = lds; - } - return lds; - }; -const dateTimeOptions: Intl.DateTimeFormatOptions = { - weekday: "short", - year: "numeric", - month: "long", - day: "numeric", -}; - -export const TaskListTableDefault: React.FC<{ - rowHeight: number; - rowWidth: string; - fontFamily: string; - fontSize: string; - locale: string; - tasks: Task[]; - selectedTaskId: string; - setSelectedTask: (taskId: string) => void; - onExpanderClick: (task: Task) => void; -}> = ({ - rowHeight, - rowWidth, - tasks, - fontFamily, - fontSize, - locale, - onExpanderClick, -}) => { - const toLocaleDateString = useMemo( - () => toLocaleDateStringFactory(locale), - [locale] - ); - - return ( -
- {tasks.map(t => { - let expanderSymbol = ""; - if (t.hideChildren === false) { - expanderSymbol = "▼"; - } else if (t.hideChildren === true) { - expanderSymbol = "▶"; - } - - return ( -
-
-
-
onExpanderClick(t)} - > - {expanderSymbol} -
-
{t.name}
-
-
-
-  {toLocaleDateString(t.start, dateTimeOptions)} -
-
-  {toLocaleDateString(t.end, dateTimeOptions)} -
-
- ); - })} -
- ); -}; diff --git a/application/frontend/src/lib/task-list/task-list.tsx b/application/frontend/src/lib/task-list/task-list.tsx deleted file mode 100644 index bbfed43..0000000 --- a/application/frontend/src/lib/task-list/task-list.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useEffect, useRef } from "react"; -import { BarTask } from "../../types/bar-task"; -import { Task } from "../../types/public-types"; - -export type TaskListProps = { - headerHeight: number; - rowWidth: string; - fontFamily: string; - fontSize: string; - rowHeight: number; - ganttHeight: number; - scrollY: number; - locale: string; - tasks: Task[]; - taskListRef: React.RefObject; - horizontalContainerClass?: string; - selectedTask: BarTask | undefined; - setSelectedTask: (task: string) => void; - onExpanderClick: (task: Task) => void; - TaskListHeader: React.FC<{ - headerHeight: number; - rowWidth: string; - fontFamily: string; - fontSize: string; - }>; - TaskListTable: React.FC<{ - rowHeight: number; - rowWidth: string; - fontFamily: string; - fontSize: string; - locale: string; - tasks: Task[]; - selectedTaskId: string; - setSelectedTask: (taskId: string) => void; - onExpanderClick: (task: Task) => void; - }>; -}; - -export const TaskList: React.FC = ({ - headerHeight, - fontFamily, - fontSize, - rowWidth, - rowHeight, - scrollY, - tasks, - selectedTask, - setSelectedTask, - onExpanderClick, - locale, - ganttHeight, - taskListRef, - horizontalContainerClass, - TaskListHeader, - TaskListTable, -}) => { - const horizontalContainerRef = useRef(null); - useEffect(() => { - if (horizontalContainerRef.current) { - horizontalContainerRef.current.scrollTop = scrollY; - } - }, [scrollY]); - - const headerProps = { - headerHeight, - fontFamily, - fontSize, - rowWidth, - }; - const selectedTaskId = selectedTask ? selectedTask.id : ""; - const tableProps = { - rowHeight, - rowWidth, - fontFamily, - fontSize, - tasks, - locale, - selectedTaskId: selectedTaskId, - setSelectedTask, - onExpanderClick, - }; - - return ( -
- -
- -
-
- ); -}; diff --git a/application/frontend/src/locales/service.ts b/application/frontend/src/locales/service.ts index 3333e88..f934816 100644 --- a/application/frontend/src/locales/service.ts +++ b/application/frontend/src/locales/service.ts @@ -1,6 +1,6 @@ +import { CONFIG } from "configs" import { createInstance } from "i18next" import I18NextHttpBackend from "i18next-http-backend" -import { CONFIG } from "src/configs" const i18nInstance = createInstance({ backend: { diff --git a/application/frontend/src/main.tsx b/application/frontend/src/main.tsx index db0d84f..49dabd9 100644 --- a/application/frontend/src/main.tsx +++ b/application/frontend/src/main.tsx @@ -1,10 +1,6 @@ -import { StrictMode } from "react" +import React from "react" import { createRoot } from "react-dom/client" -import App from "./App.tsx" +import App from "./App" -createRoot(document.getElementById("root")!).render( - - - , -) +createRoot(document.getElementById("root")!).render() diff --git a/application/frontend/src/pages/Home/Home.tsx b/application/frontend/src/pages/Home/Home.tsx index 5cb1e28..378ab05 100644 --- a/application/frontend/src/pages/Home/Home.tsx +++ b/application/frontend/src/pages/Home/Home.tsx @@ -1,12 +1,12 @@ +import { ProfileCard } from "~components/ProfileCard" +import { SearchBar } from "~components/SearchBar" + import React, { useState } from "react" import { useTranslation } from "react-i18next" import SearchIcon from "@mui/icons-material/Search" import { Box } from "@mui/material" -import { ProfileCard } from "src/components/ProfileCard" -import { SearchBar } from "src/components/SearchBar" - import { ContentContainer, HeaderSection, diff --git a/application/frontend/src/pages/project/Project.tsx b/application/frontend/src/pages/project/Project.tsx index f653b50..05cc942 100644 --- a/application/frontend/src/pages/project/Project.tsx +++ b/application/frontend/src/pages/project/Project.tsx @@ -1,14 +1,14 @@ +import { NavButton } from "~components/NavButton" +import { ProjectCard } from "~components/ProjectCard" +import { SearchBar } from "~components/SearchBar" + import React, { useState } from "react" import { useTranslation } from "react-i18next" import HomeIcon from "@mui/icons-material/Home" import SearchIcon from "@mui/icons-material/Search" -import settings from "src/assets/settings.svg" -import { NavButton } from "src/components/NavButton" -import { ProjectCard } from "src/components/ProjectCard" -import { SearchBar } from "src/components/SearchBar" - +import Settings from "../../assets/settings.svg?react" import { Container, HeaderSection, @@ -50,7 +50,7 @@ export const ProjectPage: React.FC = () => { - {t("settings")} + {t("projectList")} {/* diff --git a/application/frontend/src/pages/report/Report.tsx b/application/frontend/src/pages/report/Report.tsx index 94b9d44..6234b1c 100644 --- a/application/frontend/src/pages/report/Report.tsx +++ b/application/frontend/src/pages/report/Report.tsx @@ -1,18 +1,16 @@ +import stack from "~assets/stack.svg" +import CreateReportButton from "~components/CreateReportButton/CreateReportButton" +import { NavButton } from "~components/NavButton" +import ReportOverlay from "~components/ReportOverlay/ReportOverlay" +import ReportsTable from "~components/ReportsTable/ReportsTable" +import { SearchBar } from "~components/SearchBar" +import { ViewModeToggle } from "~components/ViewModeToggle" + import React, { useState } from "react" import { useTranslation } from "react-i18next" import HomeIcon from "@mui/icons-material/Home" -import stack from "src/assets/stack.svg" -import CreateReportButton from "src/components/CreateReportButton/CreateReportButton" -import { NavButton } from "src/components/NavButton" -import ReportOverlay from "src/components/ReportOverlay/ReportOverlay" -import ReportsTable from "src/components/ReportsTable/ReportsTable" -import { SearchBar } from "src/components/SearchBar" -import { ViewModeToggle } from "src/components/ViewModeToggle" -import { Gantt } from "src/lib/gantt/gantt" -import { Task, ViewMode } from "src/types/public-types" - import { Container, HeaderSection, @@ -70,24 +68,6 @@ const reports: Report[] = [ }, ] -const stageColors: { [key: string]: string } = { - Initial: "#4caf50", - Onboarding: "#ff9800", - "In progress": "#2196f3", - "In review": "#9e9e9e", - "In test": "#f44336", -} - -const tasks: Task[] = reports.map((report) => ({ - id: report.id.toString(), - name: report.name, - start: new Date(report.startDate), - end: new Date(report.endDate), - progress: 0, - color: stageColors[report.stage] || "#9e9e9e", - type: "project", -})) - export const ReportPage: React.FC = () => { const { t } = useTranslation() const [searchQuery, setSearchQuery] = useState("") @@ -118,44 +98,6 @@ export const ReportPage: React.FC = () => { report.name.toLowerCase().includes(searchQuery.toLowerCase()), ) - const handleDateChange = (taskId: string, newDate: Date) => { - console.log(`Date changed for task ${taskId}: ${newDate}`) - } - - const handleProgressChange = (taskId: string, newProgress: number) => { - console.log(`Progress changed for task ${taskId}: ${newProgress}`) - } - - const handleDoubleClick = (taskId: string) => { - console.log(`Task ${taskId} double-clicked`) - } - - const handleClick = (taskId: string) => { - console.log(`Task ${taskId} clicked`) - } - - const handleDelete = (taskId: string) => { - console.log(`Task ${taskId} deleted`) - } - - const handleSelect = (task: Task, isSelected: boolean) => { - console.log(`Task ${task.id} ${isSelected ? "selected" : "deselected"}`) - } - - const handleExpanderClick = (task: Task) => { - console.log(`Expander clicked for task ${task.id}`) - } - - const minDate = new Date(Math.min(...reports.map((r) => new Date(r.startDate).getTime()))) - const maxDate = new Date(Math.max(...reports.map((r) => new Date(r.endDate).getTime()))) - - const reportData = reports.map((report) => ({ - ...report, - color: stageColors[report.stage], - startDate: new Date(report.startDate), - endDate: new Date(report.endDate), - })) - return ( @@ -180,72 +122,11 @@ export const ReportPage: React.FC = () => { onClearSearch={handleClearSearch} variant="standard" placeholder={t("searchPlaceholder")} - style={{ flexGrow: 1, marginRight: "16px" }} /> - {viewMode === "list" ? ( - - ) : ( -
Tooltip for {task.name}
} - TaskListHeader={({ rowHeight }) =>
Отчеты
} - TaskListTable={({ tasks, rowHeight }) => ( -
NameStartEnd
{task.name}{task.start.toDateString()}{task.end.toDateString()}
- - - - - - - {tasks.map((task) => ( - - - - ))} - -
{task.name}
- )} - onDateChange={handleDateChange} - onProgressChange={handleProgressChange} - onDoubleClick={handleDoubleClick} - onClick={handleClick} - onDelete={handleDelete} - onSelect={handleSelect} - onExpanderClick={handleExpanderClick} - /> - )} + diff --git a/application/frontend/src/routes/routes.tsx b/application/frontend/src/routes/routes.tsx index beec392..a850da0 100644 --- a/application/frontend/src/routes/routes.tsx +++ b/application/frontend/src/routes/routes.tsx @@ -1,9 +1,10 @@ -import { RouteObject, useRoutes } from "react-router-dom" +import { Layout } from "~components/Layout/Layout" +import { Home } from "~pages/Home" +import { ProjectPage } from "~pages/project" +import { ReportPage } from "~pages/report" -import { Layout } from "src/components/Layout/Layout" -import { Home } from "src/pages/Home" -import { ProjectPage } from "src/pages/Project" -import { ReportPage } from "src/pages/Report" +import React from "react" +import { RouteObject, useRoutes } from "react-router-dom" const routes: RouteObject[] = [ { diff --git a/application/frontend/src/types/vite-env.d.ts b/application/frontend/src/types/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/application/frontend/src/types/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/application/frontend/src/vite-env.d.ts b/application/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..d052323 --- /dev/null +++ b/application/frontend/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// +/// + +interface ImportMetaEnv { + readonly VITE_BASENAME: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/application/frontend/tsconfig.app.json b/application/frontend/tsconfig.app.json deleted file mode 100644 index eea44f1..0000000 --- a/application/frontend/tsconfig.app.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "baseUrl": ".", - "paths": { - "src/*": ["./src/*"] - } - }, - "include": ["src"] -} diff --git a/application/frontend/tsconfig.json b/application/frontend/tsconfig.json index d32ff68..d826594 100644 --- a/application/frontend/tsconfig.json +++ b/application/frontend/tsconfig.json @@ -1,4 +1,30 @@ { - "files": [], - "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "baseUrl": "src", + "paths": { + "~api/*": ["./api/*"], + "~components/*": ["./components/*"], + "~types/*": ["./types/*"], + "~locales/*":["./locales/*"], + "~configs/*":["./configs/*"], + "~assets/*":["./assets/*"], + "~pages/*":["./pages/*"] + } + }, + "include": ["./src"] } diff --git a/application/frontend/tsconfig.node.json b/application/frontend/tsconfig.node.json deleted file mode 100644 index 02d80b5..0000000 --- a/application/frontend/tsconfig.node.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "baseUrl": ".", - "paths": { - "src/*": ["./src/*"] - } - }, - "include": ["vite.config.ts"] -} diff --git a/application/frontend/vite.config.ts b/application/frontend/vite.config.ts index 83a85e9..bf8ebe0 100644 --- a/application/frontend/vite.config.ts +++ b/application/frontend/vite.config.ts @@ -1,9 +1,22 @@ +import path from "path" import { defineConfig } from "vite" +import svgr from "vite-plugin-svgr" import tsconfigPaths from "vite-tsconfig-paths" import react from "@vitejs/plugin-react-swc" export default defineConfig({ - plugins: [react(), tsconfigPaths()], - base: process.env.VITE_BASENAME || "/", + plugins: [react(), tsconfigPaths(), svgr()], + base: import.meta.VITE_BASENAME || "/", + resolve: { + alias: { + "~api": path.resolve(__dirname, "./src/api"), + "~components": path.resolve(__dirname, "./src/components"), + "~types": path.resolve(__dirname, "./src/types"), + "~locales": path.resolve(__dirname, "./src/locales"), + "~configs": path.resolve(__dirname, "./src/configs"), + "~assets": path.resolve(__dirname, "./src/assets"), + "~pages": path.resolve(__dirname, "./src/pages"), + }, + }, })