diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..bb871ca Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..39e6779 --- /dev/null +++ b/src/App.css @@ -0,0 +1,13 @@ +.App { + padding: 20px; + background-color: aliceblue; +} + + +.WhiteWindow{ + background-color: white; + border: 1px rgb(1, 1, 1, 0.1) solid; + border-radius: 10px; /* Adjust the value to control the curve */ +} + + diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..4bab39f --- /dev/null +++ b/src/App.js @@ -0,0 +1,189 @@ +import React, { useEffect, useState } from 'react'; +import './App.css'; +import axios from 'axios'; + +import {VersionComponent, InfoComponent} from './Components.tsx'; + +import { Grid, Tab, Tabs, Paper, Typography, CircularProgress } from '@mui/material'; + +import { ThemeProvider, createTheme } from '@mui/material/styles'; + + +let theme = createTheme({ + palette: { + primary: { + main: '#049a9b', + } + }, +}); + + + + +const fetchDataSgi = async (streamlineSgiId) => { + try { + const res = await axios.get("https://cc.streamline.pt/api/applications/findOne", + { + params: { + filter: { + where: { + id: streamlineSgiId + }, + include: ["modules", { + relation: "versions", + scope: { + include: "module", + order: "version DESC" + } + }] + } + } + }); + + let data = res.data; + let modulesSgi = data.modules; + let versionsSgi = data.versions; + + return { modulesSgi, versionsSgi }; // Return both modules and versions + + } catch (error) { + // Handle errors if any + console.error('Error fetching data:', error); + throw error; + } +}; + + +const fetchDataApp = async (streamlineAppId) => { + try { + const res = await axios.get("https://cc.streamline.pt/api/applications/findOne", + { + params: { + filter: { + where: { + id: streamlineAppId + }, + include: ["modules", { + relation: "versions", + scope: { + include: "module", + order: "version DESC" + } + }] + } + } + }); + + let data = res.data; + + return data + + } catch (error) { + // Handle errors if any + console.error('Error fetching data:', error); + throw error; + } +}; + + + +function App() { + + const streamlineSgiId = "60a288ed33320700119606f9"; + const streamlineAppId = "6082b67f1e0f13001133287b"; + + const [value, setValue] = useState(0); + const [selectedVersion, setSelectedVersion] = useState(null); + const [modulesData, setModulesData] = useState({ modules: [], versions: [] }); + const [appData, setAppData] = useState({ modules: [], versions: [] }); + + const [loading, setLoading] = useState(true); + + + const handleChange = (event, newValue) => { + setValue(newValue); + setSelectedVersion(null); // Reset selectedVersion when switching tabs + }; + + useEffect(() => { + const fetchModules = async () => { + try { + const { modulesSgi, versionsSgi } = await fetchDataSgi(streamlineSgiId); + setModulesData({ modules: modulesSgi, versions: versionsSgi }); // Set the modules and versions in the state + + const appData = await fetchDataApp(streamlineAppId); + setAppData(appData); + + } catch (error) { + console.error('Error fetching modules:', error); // Handle errors if any + + } finally { + setLoading(false); // Set loading state to false when the fetch operation is complete + } + }; + + fetchModules(); // Call the function to fetch modules + + }, [streamlineSgiId]); + + + + return ( + + + +
+ + Histórico de versões + + + + {loading ? ( + // Display a loading circular progress while data is being fetched +
+ +
+ + ) : ( +
+ + + + {/*----TAB SELECTION AREA----------------------------------------------*/} + + + + + + + + {/*-------INFO AREA----------------------------------------*/} + + + {/*-------HISTORICO VERSOES----------------------------------------*/} + + + {value === 0 && } + {value === 1 && } + + + + {/*-------MORE INFO GRIDS (Info and improvements)----------------------------------------*/} + + + {value === 0 && } + {value === 1 && } + + + + +
+ )} + +
+
+ ); +} + + +export default App; diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 0000000..1f03afe --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,8 @@ +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/src/Components.tsx b/src/Components.tsx new file mode 100644 index 0000000..5e3207a --- /dev/null +++ b/src/Components.tsx @@ -0,0 +1,383 @@ +import React, {useState} from 'react'; +import './App.css'; +import MultipleSelectFilter from './MultipleSelectFilter.tsx'; +import MultipleSelectModule from './MultipleSelectModule.tsx'; + +import { Grid, TextField, List, ListItem, Divider, Card, Typography} from '@mui/material'; +import { Timeline, TimelineItem, TimelineSeparator, TimelineConnector, TimelineContent, TimelineDot} from '@mui/lab'; +import TimelineOppositeContent, { + timelineOppositeContentClasses, +} from '@mui/lab/TimelineOppositeContent'; + +import DonutLargeIcon from '@mui/icons-material/DonutLarge'; +import SquareFootIcon from '@mui/icons-material/SquareFoot'; +import StraightenIcon from '@mui/icons-material/Straighten'; +import AccountBalanceIcon from '@mui/icons-material/AccountBalance'; +import WarehouseIcon from '@mui/icons-material/Warehouse'; +import EuroIcon from '@mui/icons-material/Euro'; +import PowerIcon from '@mui/icons-material/Power'; +import LocalFireDepartmentIcon from '@mui/icons-material/LocalFireDepartment'; +import ConstructionIcon from '@mui/icons-material/Construction'; +import HeightIcon from '@mui/icons-material/Height'; + +import BuildIcon from '@mui/icons-material/Build'; +import DynamicFeedIcon from '@mui/icons-material/DynamicFeed'; +import HubIcon from '@mui/icons-material/Hub'; +import InfoIcon from '@mui/icons-material/Info'; + +import FileCopyIcon from '@mui/icons-material/FileCopy'; +import IconButton from '@mui/material/IconButton'; + +import { ThemeProvider, createTheme } from '@mui/material/styles'; + + + +let theme = createTheme({ + palette: { + primary: { + main: '#049a9b', + } + }, +}); + + +const listItemStyle = { margin: '-15px 5px -10px 0px',}; +const infoTitlesStyle = { fontWeight: 'bold', margin: '15px 0px 10px 8px', fontSize: '25px' }; +const informacaoTitleStyles = {fontWeight: 'bold', margin: '0px 10px 5px 10px', fontSize: '15px' } +const informacaoContentStyle = { margin: '0px 10px 5px 10px', fontSize: '15px' } + +const namesWithIcons = [ + { id: 0, name: 'Novas funcionalidade', icon: < HubIcon /> }, + { id: 1, name: 'Correção de Falhas', icon: }, + { id: 2, name: 'Melhorias', icon: }, +]; + + +function formatDate(myString){ + const date = new Date(myString); + const year = date.getUTCFullYear() % 100; // Get the last two digits of the year + const month = (date.getUTCMonth() + 1).toString().padStart(2, '0'); // Month is zero-based + const day = date.getUTCDate().toString().padStart(2, '0'); + + return `${day}/${month}/${year}`; +} + + +function semanticVersions(myString){ + // Convert from "005001010" to 5.1.10 + return String(parseInt(myString.slice(0, 3)))+"."+ + String(+ parseInt(myString.slice(3, 6)))+"."+ + String(+ parseInt(myString.slice(6, 9))) + +} + +const getIconForModule = (myModuleName) => { + switch (myModuleName) { + case 'CORE': return ; + case 'FOR': return ; + case 'IE': return ; + case 'LME': + case 'LAB': return ; + case 'ELV': return ; + case 'FIN': return ; + case 'ATV': return ; + case 'PAD': return ; + case 'OBL': + case 'OBLER': return ; + case 'GAS': return ; + case 'FUN': return ; + default: return ; + } +}; + + +function VersionComponent({message, setSelectedVersion, filters }) { + + //Check for the tab selected --> Could be "SGI" or "Mobile" + var tabSelected = ""; + if (message.name === "SGI Mobile") {tabSelected = "mobile"} + else {tabSelected = "SGI"} + + // Versions (TIMELINE) + let versions = message.versions //Get all versions + const [selectedVersion, setSelectedIcon] = useState(null); + + + // Module (CHIP) + const moduleNames: string[] = []; + for (let i = 0; i < message.modules.length; i++){ moduleNames[i] = message.modules[i].value;} + + // Filters + const [moduleFilter, setModuleFilter] = useState([]); + const [filter, setFilter] = useState([]); + + + // Handling of the version click + const handleIconClick = (index, data) => { + setSelectedIcon(index); // Aggiorna lo stato selectedVersion + setSelectedVersion(data); + }; + + // Handle search text + const [searchText, setSearchText] = useState(''); + + // Function for filtering the versions by the search text + const cleanedVersions = Object.values(versions).filter((item) => { + const versionInfo = `${item.module.value} ${semanticVersions(item.version)} ${item.improvements} ${item.bugfixes} ${item.added}`; + return versionInfo.toLowerCase().includes(searchText.toLowerCase()); + }); + + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + + + + return ( + + + + Versões {tabSelected} + + + setSearchText(e.target.value)} + sx={{ width: 'calc(100% - 30px)', marginLeft: '15px', marginRight: '15px', marginBottom: '15px'}} + /> + + + + + + + + + + + + + + + + + {cleanedVersions.map((item, index) => { + + // Filter item by the modulew selected (module chip) + if (moduleFilter.length > 0){ + if (!(moduleFilter.includes(item.module.value))){return null;} + } + + //console.log("my item", item.module.value) + + // Filter module by the improvement chip + if ((filter.includes(namesWithIcons[2].name)) && ((item.improvements === undefined) || (item.improvements === "")) ) {return null;} + // Filter module by the added chip + if ((filter.includes(namesWithIcons[0].name)) && ((item.added === undefined) || (item.added === ""))) {return null;} + // Filter module by the added chip + if ((filter.includes(namesWithIcons[1].name)) && ((item.bugfixes === undefined) || (item.bugfixes === ""))) {return null;} + + + // Return the item + return ( + + + + + {formatDate(item.date)} + + + + + + + + handleIconClick(index, item)}> + {getIconForModule(item.module.name)} + + + + + + + + + + {item.module.value} + + + {semanticVersions(item.version)} + + + + + ); + })} + + + + {console.log("NO VERSIOooN!", cleanedVersions.length)} + {console.log("\n\n\n\n\n\n\nYALLE!")} + + + + + + ); + } + +function InfoComponent({ modules, version: selectedVersion }) { + + // If no version is selected, don't render anything + if (!selectedVersion || !selectedVersion.version) { + return null; + } + + + const copyText = (text) => { + // Function that creates an icon to copy the git id + const tempInput = document.createElement('input'); + tempInput.value = text; + document.body.appendChild(tempInput); + + tempInput.select(); + tempInput.setSelectionRange(0, 999); + + document.execCommand('copy'); + + document.body.removeChild(tempInput); + console.log(`Copied: ${text}`); + }; + + return ( + + + + + + + + Informação para versão {semanticVersions(selectedVersion.version)} + + + + + + + + Módulo + + {selectedVersion.module.value} + + + + + Data de lançamento + + {formatDate(selectedVersion.date)} + + + + + Revisão GIT (se aplicavel) + + {selectedVersion.gitHash ? + ( + <> + {selectedVersion.gitHash} + copyText(selectedVersion.gitHash)} + > + + + + ) : ( + "N/D" + )} + + + + + + + + + + + + {selectedVersion.added && ( + + + {namesWithIcons[0].icon} + + + Novas Funcionalidades + + + + + + + + )} + + + + {selectedVersion.bugfixes && ( + + + {namesWithIcons[1].icon} + + Correção de Falhas + + + + + + + + + )} + + + + {selectedVersion.improvements && ( + + + {namesWithIcons[2].icon} + + Melhorias + + + + + + + + + + + )} + + + + + + + + + ); +} + + +export {VersionComponent, InfoComponent}; \ No newline at end of file diff --git a/src/MultipleSelectFilter.tsx b/src/MultipleSelectFilter.tsx new file mode 100644 index 0000000..2a49da1 --- /dev/null +++ b/src/MultipleSelectFilter.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import FormControl from '@mui/material/FormControl'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import Chip from '@mui/material/Chip'; +import Grid from '@mui/material/Grid'; + + +interface NameWithIcon { + name: string; + icon: JSX.Element; // Tipo di elemento per l'icona +} + +interface Props { + names: NameWithIcon[]; // Modifica il tipo delle props per accettare nomi con icone + filter: string[]; // Aggiungi il tipo di props per l'array filter + +} + +const MultipleSelectFilter: React.FC = ({ names, filter, setFilter }) => { + const [personName, setPersonName] = React.useState([]); + + const handleChange = (event: SelectChangeEvent) => { + const { + target: { value }, + } = event; + const selectedValues = typeof value === 'string' ? value.split(',') : value; + setPersonName(selectedValues); + setFilter(selectedValues); // Aggiorna l'array filter quando cambia la selezione + }; + + return ( + + + Filtra + + + + ); +} + +export default MultipleSelectFilter; diff --git a/src/MultipleSelectModule.tsx b/src/MultipleSelectModule.tsx new file mode 100644 index 0000000..3215fb9 --- /dev/null +++ b/src/MultipleSelectModule.tsx @@ -0,0 +1,101 @@ +import * as React from 'react'; + +import Box from '@mui/material/Box'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import FormControl from '@mui/material/FormControl'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import Chip from '@mui/material/Chip'; +import Grid from '@mui/material/Grid'; + +import DonutLargeIcon from '@mui/icons-material/DonutLarge'; +import SquareFootIcon from '@mui/icons-material/SquareFoot'; +import StraightenIcon from '@mui/icons-material/Straighten'; +import AccountBalanceIcon from '@mui/icons-material/AccountBalance'; +import WarehouseIcon from '@mui/icons-material/Warehouse'; +import EuroIcon from '@mui/icons-material/Euro'; +import PowerIcon from '@mui/icons-material/Power'; +import LocalFireDepartmentIcon from '@mui/icons-material/LocalFireDepartment'; +import ConstructionIcon from '@mui/icons-material/Construction'; +import HeightIcon from '@mui/icons-material/Height'; + +const getIconForModule = (moduleName) => { + switch (moduleName) { + case 'CORE': return ; + case 'FOR': return ; + case 'IE': return ; + case 'LME': + case 'LAB': return ; + case 'ELV': return ; + case 'FIN': return ; + case 'ATV': return ; + case 'PAD': return ; + case 'OBL': + case 'OBLER': return ; + case 'GAS': return ; + case 'FUN': return ; + default: return ; + } +}; + +interface Props { + modules: { value: string }[]; + moduleFilter: string[]; // Aggiungi il tipo di props per l'array filter + setModuleFilter: (filter: string[]) => void; +} + +const MultipleSelectModule: React.FC = ({ modules, moduleFilter, setModuleFilter }) => { + const [selectedModules, setSelectedModules] = React.useState([]); + + const handleChange = (event: SelectChangeEvent) => { + console.log("QUi--> ", event.target, event.target.value, event.target.name) + setSelectedModules(event.target.value as string[]); + + const selectedValues = (event.target.value as { value: string, name: string }[]).map(module => module.value); + setModuleFilter(selectedValues); // Update moduleFilter with only the 'value' properties + }; + + return ( + + + Selecionar modulo + + + + + ); +} + +export default MultipleSelectModule; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..ec2585e --- /dev/null +++ b/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..d563c0f --- /dev/null +++ b/src/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js new file mode 100644 index 0000000..5253d3a --- /dev/null +++ b/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom';