First commit

main
Antonio Quattromini 1 year ago
parent 4aeccb8213
commit fb0cc822f0

BIN
src/.DS_Store vendored

Binary file not shown.

@ -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 */
}

@ -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 (
<ThemeProvider theme={theme}>
<div className="App">
<Typography variant="h3" sx={{ fontWeight: 'bold', marginBottom: '10px' }}>
Histórico de versões
</Typography>
{loading ? (
// Display a loading circular progress while data is being fetched
<div sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '200%' }}>
<CircularProgress />
</div>
) : (
<div className="WhiteWindow">
<Grid container>
{/*----TAB SELECTION AREA----------------------------------------------*/}
<Grid item md={12}>
<Tabs value={value} onChange={handleChange}>
<Tab label="SISTEMA DE GESTÃO INTEGRADO (WEB)" />
<Tab label="APLICAÇÃO MÓVEL" />
</Tabs>
</Grid>
{/*-------INFO AREA----------------------------------------*/}
<Grid container sx={{ margin: '5px', padding: '5px', marginBottom: '20px' }}>
{/*-------HISTORICO VERSOES----------------------------------------*/}
<Grid item md={6} sx={{ padding: "10px" }}>
<Paper>
{value === 0 && <VersionComponent message={modulesData} setSelectedVersion={setSelectedVersion}/>}
{value === 1 && <VersionComponent message={appData} setSelectedVersion={setSelectedVersion} />}
</Paper>
</Grid>
{/*-------MORE INFO GRIDS (Info and improvements)----------------------------------------*/}
<Grid item md={6} sx={{ padding: "10px" }}>
<Paper>
{value === 0 && <InfoComponent modules={modulesData} version={selectedVersion} />}
{value === 1 && <InfoComponent modules={appData} version={selectedVersion} />}
</Paper>
</Grid>
</Grid>
</Grid>
</div>
)}
</div>
</ThemeProvider>
);
}
export default App;

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

@ -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: <BuildIcon /> },
{ id: 2, name: 'Melhorias', icon: <DynamicFeedIcon /> },
];
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 <DonutLargeIcon />;
case 'FOR': return <SquareFootIcon />;
case 'IE': return <PowerIcon/>;
case 'LME':
case 'LAB': return <StraightenIcon/>;
case 'ELV': return <HeightIcon />;
case 'FIN': return <AccountBalanceIcon />;
case 'ATV': return <AccountBalanceIcon />;
case 'PAD': return <EuroIcon />;
case 'OBL':
case 'OBLER': return <WarehouseIcon/>;
case 'GAS': return <LocalFireDepartmentIcon/>;
case 'FUN': return <ConstructionIcon />;
default: return <AccountBalanceIcon />;
}
};
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 (
<ThemeProvider theme={theme}>
<Card className="Versoes-Card" variant="outlined">
<Typography sx={{ fontWeight: 'bold', margin: '15px 15px 10px 15px', fontSize: '25px' }}>
Versões {tabSelected}
</Typography>
<TextField
size="small"
id="outlined"
label="Pesquisa..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
sx={{ width: 'calc(100% - 30px)', marginLeft: '15px', marginRight: '15px', marginBottom: '15px'}}
/>
<Grid container sx={{ marginBottom: '10px'}}>
<Grid item sx={{marginLeft: "8px", marginTop: "-5px"}}>
<MultipleSelectModule modules={message.modules} moduleFilter={moduleFilter} setModuleFilter={setModuleFilter}></MultipleSelectModule>
</Grid>
<Grid item sx={{marginLeft: "8px", marginTop: "-5px", paddingRight: '5px'}}>
<MultipleSelectFilter names={namesWithIcons} filter={filter} setFilter={setFilter}></MultipleSelectFilter>
</Grid>
</Grid>
<Timeline style={{maxHeight: '800px', overflow: 'auto'}}
sx={{[`& .${timelineOppositeContentClasses.root}`]: {flex: 0.1, },}}>
{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 (
<TimelineItem key={index} sx={{ py: '4px' }}>
<TimelineOppositeContent color="textSecondary" sx={{ py: '22px' }}>
<Typography variant="body2" component="div">
{formatDate(item.date)}
</Typography>
</TimelineOppositeContent>
<TimelineSeparator>
<TimelineDot
color={selectedVersion === index ? 'warning' : 'primary'}
onClick={() => handleIconClick(index, item)}>
{getIconForModule(item.module.name)}
</TimelineDot>
<TimelineConnector sx={{ paddingTop: '22px' }} />
</TimelineSeparator>
<TimelineContent>
<Typography variant="h6" sx={{ fontSize: '18px', lineHeight: '18px', padding: '8px 0px 0px 0px' }}>
{item.module.value}
</Typography>
<Typography variant="body2" color="textPrimary">
{semanticVersions(item.version)}
</Typography>
</TimelineContent>
</TimelineItem>
);
})}
</Timeline>
{console.log("NO VERSIOooN!", cleanedVersions.length)}
{console.log("\n\n\n\n\n\n\nYALLE!")}
</Card>
</ThemeProvider>
);
}
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 (
<ThemeProvider theme={theme}>
<Card variant="outlined">
<List >
<ListItem sx={listItemStyle}>
<InfoIcon></InfoIcon>
<Typography sx={infoTitlesStyle}>
Informação para versão {semanticVersions(selectedVersion.version)}
</Typography>
</ListItem>
<ListItem>
<Grid container spacing={3}>
<Grid item md={6}>
<Typography sx={informacaoTitleStyles}> Módulo </Typography>
<Typography sx={informacaoContentStyle}>
{selectedVersion.module.value}
</Typography>
</Grid>
<Grid item md={6}>
<Typography sx={informacaoTitleStyles}> Data de lançamento </Typography>
<Typography sx={informacaoContentStyle}>
{formatDate(selectedVersion.date)}
</Typography>
</Grid>
<Grid item md={12}>
<Typography sx={informacaoTitleStyles}> Revisão GIT (se aplicavel) </Typography>
<Typography sx={informacaoContentStyle}>
{selectedVersion.gitHash ?
(
<>
{selectedVersion.gitHash}
<IconButton sx={{ transform: 'scale(0.6)', margin: '-10px 0px 0px 0px' }}
title="Copia"
onClick={() => copyText(selectedVersion.gitHash)}
>
<FileCopyIcon />
</IconButton>
</>
) : (
"N/D"
)}
</Typography>
</Grid>
</Grid>
</ListItem>
</List>
<Divider/>
{selectedVersion.added && (
<List>
<ListItem sx={listItemStyle}>
{namesWithIcons[0].icon}
<Typography sx={infoTitlesStyle}>
Novas Funcionalidades
</Typography>
</ListItem>
<ListItem>
<Typography sx={{ margin: '0px 0px 10px -10px', fontSize: '15px' }}
dangerouslySetInnerHTML={{ __html: selectedVersion.added }} />
</ListItem>
</List>
)}
<Divider/>
{selectedVersion.bugfixes && (
<List>
<ListItem sx={listItemStyle}>
{namesWithIcons[1].icon}
<Typography sx={infoTitlesStyle}>
Correção de Falhas
</Typography>
</ListItem>
<ListItem>
<Typography sx={{ margin: '0px 0px 10px -10px', fontSize: '15px' }}
dangerouslySetInnerHTML={{ __html: selectedVersion.bugfixes }} />
</ListItem>
</List>
)}
<Divider/>
{selectedVersion.improvements && (
<List>
<ListItem sx={listItemStyle}>
{namesWithIcons[2].icon}
<Typography sx={infoTitlesStyle}>
Melhorias
</Typography>
</ListItem>
<ListItem>
<Typography sx={{ margin: '0px 0px 10px -10px', fontSize: '15px' }}
dangerouslySetInnerHTML={{ __html: selectedVersion.improvements }} />
</ListItem>
</List>
)}
<Divider/>
</Card>
</ThemeProvider>
);
}
export {VersionComponent, InfoComponent};

@ -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<Props> = ({ names, filter, setFilter }) => {
const [personName, setPersonName] = React.useState<string[]>([]);
const handleChange = (event: SelectChangeEvent<typeof personName>) => {
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 (
<Grid>
<FormControl sx={{ m: 1, minWidth: 240}} >
<InputLabel id="demo-multiple-chip-label" sx={{ marginTop: '-8px' }}>Filtra </InputLabel>
<Select
labelId="demo-multiple-chip-label"
//id="demo-multiple-chip"
multiple
size="small"
value={personName}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: '0.8px' }}>
{selected.map((value) => (
<Chip
key={value}
label={names.find((item) => item.name === value)?.icon}
sx={{ fontSize: '2rem'}}/>
))}
</Box>
)}
>
{names.map((nameWithIcon) => (
<MenuItem key={nameWithIcon.name} value={nameWithIcon.name}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{nameWithIcon.icon}
<span style={{ marginLeft: '10px' }}>{nameWithIcon.name}</span>
</Box>
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
);
}
export default MultipleSelectFilter;

@ -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 <DonutLargeIcon />;
case 'FOR': return <SquareFootIcon />;
case 'IE': return <PowerIcon/>;
case 'LME':
case 'LAB': return <StraightenIcon/>;
case 'ELV': return <HeightIcon />;
case 'FIN': return <AccountBalanceIcon />;
case 'ATV': return <AccountBalanceIcon />;
case 'PAD': return <EuroIcon />;
case 'OBL':
case 'OBLER': return <WarehouseIcon/>;
case 'GAS': return <LocalFireDepartmentIcon/>;
case 'FUN': return <ConstructionIcon />;
default: return <AccountBalanceIcon />;
}
};
interface Props {
modules: { value: string }[];
moduleFilter: string[]; // Aggiungi il tipo di props per l'array filter
setModuleFilter: (filter: string[]) => void;
}
const MultipleSelectModule: React.FC<Props> = ({ modules, moduleFilter, setModuleFilter }) => {
const [selectedModules, setSelectedModules] = React.useState<string[]>([]);
const handleChange = (event: SelectChangeEvent<typeof selectedModules>) => {
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 (
<Grid>
<FormControl sx={{ m: 1, minWidth: 240}}>
<InputLabel id="demo-multiple-chip-label" sx={{ marginTop: '-8px' }}>Selecionar modulo</InputLabel>
<Select
fullWidth
labelId="demo-multiple-chip-label"
//id="demo-multiple-chip"
multiple
size="small"
value={selectedModules}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) => {
return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: '0.1px' }}>
{selected.map((value) => (
<Chip
key={value}
icon={getIconForModule(value.name)}
sx={{
fontSize: '2rem', backgroundColor: 'transparent'}} />
))}
</Box>
);
}}
>
{modules.map((modulee) => (
<MenuItem key={modulee} value={modulee}>
<Box sx={{ display: 'flex', alignItems: 'right' }}>
{getIconForModule(modulee.name)}
<span style={{ marginLeft: '10px' }}>{modulee.value}</span>
</Box>
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
);
}
export default MultipleSelectModule;

@ -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;
}

@ -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(
<React.StrictMode>
<App />
</React.StrictMode>
);
// 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();

@ -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;

@ -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';
Loading…
Cancel
Save