Skip to content

Commit

Permalink
feat: added range slider with animation to statistics view
Browse files Browse the repository at this point in the history
  • Loading branch information
themrphantom committed Dec 23, 2024
1 parent 77e9127 commit 160fc46
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 37 deletions.
12 changes: 9 additions & 3 deletions frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";

import hooksPlugin from "eslint-plugin-react-hooks";

export default [
pluginJs.configs.recommended,
Expand All @@ -17,6 +17,9 @@ export default [
},
{
files: ["**/*.tsx", "**/*.ts"],
plugins: {
"react-hooks": hooksPlugin,
},
rules: {
"react/react-in-jsx-scope": 'off',
"@typescript-eslint/no-unused-vars": [
Expand All @@ -30,11 +33,14 @@ export default [
"@typescript-eslint/no-wrapper-object-types": 'off',
"react/jsx-key": 'off',
"@typescript-eslint/no-explicit-any": 'off',

// React Hooks Rules
"react-hooks/rules-of-hooks": "error", // Enforces the rules of hooks
"react-hooks/exhaustive-deps": "warn", // Enforces dependency array of hooks
},

},
{
languageOptions: { globals: globals.browser },
},

];
];
14 changes: 14 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.17.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-unused-imports": "^4.1.4",
"globals": "^15.14.0",
"redux-devtools-extension": "^2.13.9",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/Components/Admin/Overview/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Infobox from '../../Common/InfoBox/Infobox';
import { Area, AreaChart, Bar, BarChart, CartesianGrid, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import TopDepter from '../Common/TopDepter/TopDepter';
import { RootState } from '../../../Reducer/reducerCombiner';
import { BENUTZER_ZAHL, BUDGET, EINSTELLUNGEN, GELD_VERTEILUNG, GETRAENKE, LETZE_100_KAEUFE, MITGLIEDER, TRANSAKTIONEN, VERSTECKTE_NUTZER } from '../../Common/Internationalization/i18n';
import { BENUTZER_ZAHL, BUDGET, EINSTELLUNGEN, GELD_VERTEILUNG, GETRAENKE, LETZTE_100_KAEUFE, MITGLIEDER, TRANSAKTIONEN, VERSTECKTE_NUTZER } from '../../Common/Internationalization/i18n';
import { Transaction } from '../../../types/ResponseTypes';


Expand Down Expand Up @@ -136,7 +136,7 @@ const Overview = (props: Props) => {
<Area type="monotone" dataKey="Guthaben" stroke={window.globalTS.ICON_COLOR} fillOpacity={0.5} fill={window.globalTS.ICON_COLOR} />
</AreaChart >
</Infobox>
<Infobox headline={LETZE_100_KAEUFE} >
<Infobox headline={LETZTE_100_KAEUFE} >
<BarChart width={window.innerWidth / 3} height={200} data={getDiagramData()}>
<CartesianGrid strokeDasharray="3 3" />
<YAxis dataKey="Anzahl Transaktionen" />
Expand Down
150 changes: 119 additions & 31 deletions frontend/src/Components/Admin/Statistics/Statistics.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Stack, Typography } from '@mui/material'
import { Button, Slider, Stack, Typography } from '@mui/material'
import React, { useEffect, useState } from 'react'
import StatisticBox from '../../Common/InfoBox/StatisticBox'
import { BENUTZER_ZAHL, BUDGET, GELD_VERTEILUNG, LETZE_100_KAEUFE, MITGLIEDER, TOP_10_GETRAENKE, UMSATZ, UMSATZ_NACH_WOCHENTAG, VERSTECKTE_NUTZER } from '../../Common/Internationalization/i18n'
import { BENUTZER_ZAHL, BUDGET, GELD_VERTEILUNG, GESAMT_UMSATZ_GETRAENKE, LETZTE_KAEUFE, MITGLIEDER, TOP_10_GETRAENKE, UMSATZ, UMSATZ_NACH_WOCHENTAG, VERSTECKTE_NUTZER } from '../../Common/Internationalization/i18n'
import { Money, Person, VisibilityOff } from '@mui/icons-material'
import TopDepter from '../Common/TopDepter/TopDepter'
import { CommonReducerType } from '../../../Reducer/CommonReducer'
Expand All @@ -13,15 +13,18 @@ import { Transaction } from '../../../types/ResponseTypes'
import style from './statistics.module.scss';
import Infobox from '../../Common/InfoBox/Infobox'
import { Area, AreaChart, Bar, BarChart, CartesianGrid, Legend, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import PauseIcon from '@mui/icons-material/Pause';

type Props = {}

const Statistics = (props: Props) => {


const common: CommonReducerType = useSelector((state: RootState) => state.common);
const dispatch = useDispatch()
const [transactions, settransactions] = useState<Array<Transaction>>([])
const [dateRange, setdateRange] = useState([70, 100])
const [isRunning, setisRunning] = useState(false)

useEffect(() => {
doGetRequest("users").then((value) => {
Expand All @@ -32,12 +35,26 @@ const Statistics = (props: Props) => {
}, [dispatch])

useEffect(() => {
doGetRequest("transactions/limit/" + window.globalTS.TRANSACTION_LIMIT).then((value) => {
doGetRequest("transactions/limit/10000").then((value) => {
if (value.code === 200) {
settransactions(value.content)
const t = (value.content as Array<Transaction>);
settransactions(t.reverse())
}
})
}, [])
}, [dispatch])

useEffect(() => {
if (isRunning) {
const interval = setInterval(() => {
const toSet = [Math.min(dateRange[0] + 1, 99), Math.min(dateRange[1] + 1, 100)]
if (toSet[0] === 99 && toSet[1] === 100) {
setisRunning(false)
}
setdateRange(toSet)
}, 1000)
return () => clearInterval(interval)
}
}, [isRunning, dateRange])

const calcBudget = () => {
let budget = 0
Expand All @@ -50,9 +67,20 @@ const Statistics = (props: Props) => {
common.members?.forEach(member => amount += member.hidden ? 1 : 0)
return amount
}
//get the upper and lower bound of the date range
const transactionsIndices = (): Array<number> => {
if (transactions.length === 0) { return [0, 0] }
const percent = (transactions.length - 1) / 100

const lowerBound = Math.floor(dateRange[0] * percent)
const upperBound = Math.ceil(dateRange[1] * percent)
return [lowerBound, upperBound]
}

const indices = transactionsIndices()

const getTransactionHistoryDiagramData = () => {
const sortedTransactions = transactions.sort((value1, value2) => new Date(value2.date).valueOf() - new Date(value1.date).valueOf()).slice(-100, transactions.length)
const sortedTransactions = transactions.slice(indices[0], indices[1]).toSorted((value1, value2) => new Date(value2.date).valueOf() - new Date(value1.date).valueOf())
const output = new Map<string, { date: Date, number: number }>()

sortedTransactions.forEach(value => {
Expand Down Expand Up @@ -87,7 +115,7 @@ const Statistics = (props: Props) => {
{ day: "Samstag", total: 0, amount: 0, avg: 0 }
]

transactions.forEach(value => {
transactions.slice(indices[0], indices[1]).forEach(value => {
if (value.description.includes("Checkout") || value.description.includes("Deposit") || value.description.includes("Transfer")) { return }
if (value.amount > 0) { return }

Expand Down Expand Up @@ -119,7 +147,7 @@ const Statistics = (props: Props) => {
const getMostBoughtDrinks = () => {
const output = new Map<string, number>()

transactions.forEach(value => {
transactions.slice(indices[0], indices[1]).forEach(value => {
if (value.description.includes("Checkout") || value.description.includes("Deposit") || value.description.includes("Transfer")) { return }
if (value.amount > 0) { return }

Expand All @@ -134,10 +162,32 @@ const Statistics = (props: Props) => {
const sortedList = dataList.sort((value1, value2) => {
return value2["amount"] - value1["amount"]
}).slice(0, 10)
console.log(sortedList)
return sortedList
}

const totalSales = () => {
let sales = 0
transactions.slice(indices[0], indices[1]).forEach(value => {
if (value.description.includes("Checkout") || value.description.includes("Deposit") || value.description.includes("Transfer")) { return }
if (value.amount > 0) { return }

sales += Math.abs(value.amount)

})
//Round the amount to two decimal places
sales = Math.round(sales * 100) / 100
return sales
}

const getDateRangeString = () => {
if (transactions.length === 0) {
return "??? - ???";
}
const startDate = dateToString(new Date(transactions[indices[0]].date));
const endDate = dateToString(new Date(transactions[indices[1]].date));
return `Zeitraum ${startDate} - ${endDate}`;
};

return (
<Stack className={style.container}>
<Typography variant="h4">{MITGLIEDER}</Typography>
Expand All @@ -160,33 +210,71 @@ const Statistics = (props: Props) => {
<TopDepter members={common.members} />
</Stack>
<Typography variant="h4">{UMSATZ}</Typography>
<Stack direction="row" spacing={2} flexWrap={"wrap"}>
<Infobox headline={GELD_VERTEILUNG} >
<AreaChart width={window.innerWidth / 3} height={200} data={common.members?.sort(
(m1, m2) => m1.balance - m2.balance).map(
(value) => {
return {
Guthaben: value.balance
}
<Infobox headline={GELD_VERTEILUNG} >
<AreaChart width={window.innerWidth / 3} height={200} data={common.members?.sort(
(m1, m2) => m1.balance - m2.balance).map(
(value) => {
return {
Guthaben: value.balance
}
)
}>
}
)
}>
<CartesianGrid strokeDasharray="3 3" />
<YAxis unit="€" />
<Tooltip contentStyle={{ color: "black" }} />
<Legend />
<Area type="monotone" dataKey="Guthaben" stroke={window.globalTS.ICON_COLOR} fillOpacity={0.5} fill={window.globalTS.ICON_COLOR} />
</AreaChart >
</Infobox>
<Stack direction="row" spacing={2} flexWrap={"wrap"} alignItems={"center"}>
<Typography variant="overline">{getDateRangeString()}</Typography>

<Button color='success' onClick={() => {
setisRunning(!isRunning)
}}>
{!isRunning ? <PlayArrowIcon /> : <PauseIcon />}
</Button>
</Stack>

<Slider
style={{ maxWidth: innerWidth - 50 }}
getAriaLabel={() => "Zeitraum"}
value={dateRange}
onChange={(_, value) => {
setdateRange(value as [number, number])
}}
valueLabelFormat={(value) => {
if (transactions.length === 0) { return "" }
const indices = transactionsIndices()
if (value === dateRange[0]) {
return dateToString(new Date(transactions[indices[0]].date))
}
if (value === dateRange[1]) {
return dateToString(new Date(transactions[indices[1]].date))
}


}
}
valueLabelDisplay="auto"
/>
<Stack direction="row" spacing={2} flexWrap={"wrap"}>
<StatisticBox
headline={GESAMT_UMSATZ_GETRAENKE}
text={totalSales().toFixed(2) + "€"}
icon={<Money />}
colorCode={window.globalTS.ICON_COLOR} />

<Infobox headline={LETZTE_KAEUFE} >
<AreaChart width={window.innerWidth / 3} height={200} data={getTransactionHistoryDiagramData()}>
<CartesianGrid strokeDasharray="3 3" />
<YAxis unit="€" />
<Tooltip contentStyle={{ color: "black" }} />
<Legend />
<Area type="monotone" dataKey="Guthaben" stroke={window.globalTS.ICON_COLOR} fillOpacity={0.5} fill={window.globalTS.ICON_COLOR} />
</AreaChart >
</Infobox>
<Infobox headline={LETZE_100_KAEUFE} >
<BarChart width={window.innerWidth / 3} height={200} data={getTransactionHistoryDiagramData()}>
<CartesianGrid strokeDasharray="3 3" />
<YAxis dataKey="Anzahl Transaktionen" />
<XAxis dataKey="date" />
<Tooltip contentStyle={{ color: "black" }} />
<Legend />
<Bar dataKey="Anzahl Transaktionen" fill={window.globalTS.ICON_COLOR} />
</BarChart >
<Area type="monotone" dataKey="Anzahl Transaktionen" name='Umsatz' stroke={window.globalTS.ICON_COLOR} fillOpacity={0.5} fill={window.globalTS.ICON_COLOR} />
</AreaChart >
</Infobox>
<Infobox headline={UMSATZ_NACH_WOCHENTAG} >
<AreaChart width={window.innerWidth / 3} height={200} data={getTransactionHistoryDiagramDataWeekday()}>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/Components/Common/Internationalization/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const GELD_ANFORDERN = "Geld anfordern";
export const GELD_UEBERWEISEN = "Geld überweisen";
export const GELD_UEBERWIESEN = "Geld überwiesen";
export const GELD_VERTEILUNG = "Geld Verteilung";
export const GESAMT_UMSATZ_GETRAENKE = "Gesamtumsatz (Getränke)";
export const GETRAENK_BEARBEITEN = "Getränk bearbeiten"
export const GETRAENK_BEARBEITEN_BESCHREIBUNG = "Ändere den Namen oder Kategorie des Getränks '{0}'";
export const GETRAENK_HINZUGEFUEGT = "Getränk hinzugefügt";
Expand Down Expand Up @@ -88,7 +89,8 @@ export const KONNTE_NICHT_EINSPIELEN = "Konnte Backup nicht einspielen";
export const KONTO = "Konto";
export const KONTOSTAND = "Kontostand";
export const LASSE_ACCOUNT_ACTIVIEREN = "Bitte wende dich an einen Admin, um deinen Account aktivieren zu lassen";
export const LETZE_100_KAEUFE = "Letze 100 Käufe";
export const LETZTE_100_KAEUFE = "Letze 100 Käufe";
export const LETZTE_KAEUFE = "Letzte Käufe";
export const LOGIN = "Login";
export const MEHR_OPTIONEN = "Mehr optionen";
export const MEISTE_SCHULDEN = "Meiste Schulden";
Expand Down

0 comments on commit 160fc46

Please sign in to comment.