Skip to content
Snippets Groups Projects
Commit 0a9042b4 authored by jankuepper's avatar jankuepper
Browse files

Projekt kopiert

parent ed6b46d7
No related branches found
No related tags found
No related merge requests found
Pipeline #1813 failed
Showing
with 8251 additions and 79 deletions
.env 0 → 100644
DATABASE_URL=mysql://jan:admin@localhost:3306/event_management
PORT=3001
\ No newline at end of file
{
"extends": "next/core-web-vitals"
}
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# zeitnahme
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.cvh-server.de/jkuepper/zeitnahme.git
git branch -M main
git push -uf origin main
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.cvh-server.de/jkuepper/zeitnahme/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Learn More
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
To learn more about Next.js, take a look at the following resources:
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## Deploy on Vercel
## License
For open source projects, say how it is licensed.
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
import React, { useState } from 'react';
import {
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, TextField, Button, IconButton, CircularProgress, Container
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import axios from 'axios';
const EditableTable = ({ eventId, initialParticipants }) => {
const [participants, setParticipants] = useState(initialParticipants);
const [loading, setLoading] = useState(false);
const [toDelete, setToDelete] = useState([]);
const handleChange = (index, field, value) => {
const newParticipants = [...participants];
newParticipants[index][field] = value;
setParticipants(newParticipants);
};
const handleMarkForDeletion = (participantId) => {
if (toDelete.includes(participantId)) {
setToDelete(toDelete.filter(id => id !== participantId));
} else {
setToDelete([...toDelete, participantId]);
}
};
const handleSave = async () => {
setLoading(true);
try {
// Update participants
for (const participant of participants) {
if (!toDelete.includes(participant.participantsId)) {
await axios.put(`/api/events/${eventId}/participants`, participant);
}
}
// Delete participants
for (const participantId of toDelete) {
await axios.delete(`/api/events/${eventId}/participants`, { data: { participantsId: participantId } });
}
setLoading(false);
//alert('Daten erfolgreich gespeichert');
} catch (error) {
console.error('Fehler beim Speichern der Daten:', error);
setLoading(false);
}
};
return (
<div>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>Vorname</TableCell>
<TableCell>Nachname</TableCell>
<TableCell>E-Mail</TableCell>
<TableCell>Startnummer</TableCell>
<TableCell>Transponder</TableCell>
<TableCell>Aktionen</TableCell>
</TableRow>
</TableHead>
<TableBody>
{participants.map((participant, index) => (
<TableRow
key={participant.participantsId}
style={{ backgroundColor: toDelete.includes(participant.participantsId) ? 'lightgray' : 'white' }}
>
<TableCell>
<TextField
value={participant.first_name}
onChange={(e) => handleChange(index, 'first_name', e.target.value)}
disabled={toDelete.includes(participant.participantsId)}
/>
</TableCell>
<TableCell>
<TextField
value={participant.last_name}
onChange={(e) => handleChange(index, 'last_name', e.target.value)}
disabled={toDelete.includes(participant.participantsId)}
/>
</TableCell>
<TableCell>
<TextField
value={participant.email}
onChange={(e) => handleChange(index, 'email', e.target.value)}
disabled={toDelete.includes(participant.participantsId)}
/>
</TableCell>
<TableCell>
<TextField
value={participant.startNumber}
onChange={(e) => handleChange(index, 'startNumber', e.target.value)}
disabled={toDelete.includes(participant.participantsId)}
/>
</TableCell>
<TableCell>
<TextField
value={participant.transponder1}
onChange={(e) => handleChange(index, 'transponder1', e.target.value)}
disabled={toDelete.includes(participant.participantsId)}
/>
</TableCell>
<TableCell>
<IconButton
onClick={() => handleMarkForDeletion(participant.participantsId)}
disabled={loading}
>
<DeleteIcon color={toDelete.includes(participant.participantsId) ? 'error' : 'inherit'} />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<Button
variant="contained"
color="primary"
onClick={handleSave}
disabled={loading}
style={{ marginTop: 20 }}
>
{loading ? <CircularProgress size={24} /> : 'Speichern'}
</Button>
</div>
);
};
export default EditableTable;
import React, { useState, useEffect } from 'react';
import { Container, Typography, TextField, Button, Grid, Chip } from '@mui/material';
import axios from 'axios';
export default function EventForm({ initialValues, onSubmit }) {
const formatDateTimeLocal = (dateTimeString) => {
if (!dateTimeString) return '';
const date = new Date(dateTimeString);
return date.toISOString().slice(0, 16);
};
const formatDateTimeUTC = (dateTimeString) => {
const date = new Date(dateTimeString);
const offset = date.getTimezoneOffset(); // Minuten Offset zur UTC
const adjustedDate = new Date(date.getTime() - (offset * 60 * 1000));
return adjustedDate.toISOString();
};
const [formData, setFormData] = useState(initialValues || {
name: '',
location: '',
website: '',
description: '',
startTime: '',
endTime: '',
groups: {
roundRace: [],
timeRace: [],
startEndRace: []
}
});
const [groups, setGroups] = useState([]);
useEffect(() => {
axios.get('/api/groups')
.then(response => setGroups(response.data))
.catch(error => console.error('Error loading groups:', error));
}, []);
useEffect(() => {
if (initialValues) {
setFormData({
...initialValues,
startTime: formatDateTimeLocal(initialValues.startTime),
endTime: formatDateTimeLocal(initialValues.endTime)
});
}
}, [initialValues]);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prevData => ({
...prevData,
[name]: value
}));
};
const handleGroupToggle = (groupId, category) => {
setFormData(prevData => {
let updatedGroups = {
roundRace: prevData.groups.roundRace.filter(g => g.groupId !== groupId),
timeRace: prevData.groups.timeRace.filter(g => g.groupId !== groupId),
startEndRace: prevData.groups.startEndRace.filter(g => g.groupId !== groupId)
};
if (!prevData.groups[category].some(g => g.groupId === groupId)) {
updatedGroups[category].push({ groupId, value: '' });
}
return {
...prevData,
groups: updatedGroups
};
});
};
const handleValueChange = (groupId, category, value) => {
setFormData(prevData => {
let updatedGroups = prevData.groups[category].map(g =>
g.groupId === groupId ? { ...g, value } : g
);
return {
...prevData,
groups: {
...prevData.groups,
[category]: updatedGroups
}
};
});
};
const handleSubmit = (e) => {
e.preventDefault();
const formattedData = {
...formData,
startTime: formatDateTimeUTC(formData.startTime),
endTime: formatDateTimeUTC(formData.endTime),
groups: [
...formData.groups.roundRace.map(g => ({ groupId: g.groupId, raceType: 1, value: parseInt(g.value) || 0 })),
...formData.groups.timeRace.map(g => ({ groupId: g.groupId, raceType: 2, value: parseInt(g.value) || 0 })),
...formData.groups.startEndRace.map(g => ({ groupId: g.groupId, raceType: 3, value: parseInt(g.value) || 0 }))
]
};
onSubmit(formattedData);
};
return (
<Container>
<Typography variant="h4" component="h4" gutterBottom>
{initialValues ? `Veranstaltung bearbeiten` : 'Neue Veranstaltung'}
</Typography>
<form onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
name="name"
label="Name"
variant="outlined"
fullWidth
value={formData.name || ''}
onChange={handleChange}
required
/>
</Grid>
<Grid item xs={12}>
<TextField
name="location"
label="Ort"
variant="outlined"
fullWidth
value={formData.location || ''}
onChange={handleChange}
/>
</Grid>
<Grid item xs={12}>
<TextField
name="website"
label="Webseite"
variant="outlined"
fullWidth
value={formData.website || ''}
onChange={handleChange}
/>
</Grid>
<Grid item xs={12}>
<TextField
name="description"
label="Beschreibung"
variant="outlined"
fullWidth
multiline
rows={4}
value={formData.description || ''}
onChange={handleChange}
/>
</Grid>
<Grid item xs={12}>
<TextField
name="startTime"
label="Startzeit"
type="datetime-local"
variant="outlined"
fullWidth
value={ formData.startTime || ''}
onChange={handleChange}
InputLabelProps={{
shrink: true,
}}
required
/>
</Grid>
<Grid item xs={12}>
<TextField
name="endTime"
label="Endzeit"
type="datetime-local"
variant="outlined"
fullWidth
value={ formData.endTime || ''}
onChange={handleChange}
InputLabelProps={{
shrink: true,
}}
required
/>
</Grid>
{/* Der Rest des Codes bleibt unverändert */}
<Grid item xs={12}>
<Typography variant="h6">Rundenrennen</Typography>
<Grid container spacing={1}>
{groups.map(group => {
const isSelected = formData.groups.roundRace.some(g => g.groupId === group.groupId);
return (
<Grid item key={group.groupId}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Chip
label={group.name}
clickable
color={isSelected ? 'primary' : 'default'}
onClick={() => handleGroupToggle(group.groupId, 'roundRace')}
style={{ marginRight: 8 }}
/>
{isSelected && (
<TextField
value={formData.groups.roundRace.find(g => g.groupId === group.groupId)?.value || ''}
onChange={(e) => handleValueChange(group.groupId, 'roundRace', e.target.value)}
placeholder="Anzahl"
size="small"
variant="outlined"
style={{ width: '70px' }}
/>
)}
</div>
</Grid>
);
})}
</Grid>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">Zeitrennen</Typography>
<Grid container spacing={1}>
{groups.map(group => {
const isSelected = formData.groups.timeRace.some(g => g.groupId === group.groupId);
return (
<Grid item key={group.groupId}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Chip
label={group.name}
clickable
color={isSelected ? 'primary' : 'default'}
onClick={() => handleGroupToggle(group.groupId, 'timeRace')}
style={{ marginRight: 8 }}
/>
{isSelected && (
<TextField
value={formData.groups.timeRace.find(g => g.groupId === group.groupId)?.value / 1000 || ''}
onChange={(e) => handleValueChange(group.groupId, 'timeRace', e.target.value * 1000 )}
placeholder="Secunden"
size="small"
variant="outlined"
style={{ width: '70px' }}
/>
)}
</div>
</Grid>
);
})}
</Grid>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">Start/Ziel Rennen</Typography>
<Grid container spacing={1}>
{groups.map(group => {
const isSelected = formData.groups.startEndRace.some(g => g.groupId === group.groupId);
return (
<Grid item key={group.groupId}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Chip
label={group.name}
clickable
color={isSelected ? 'primary' : 'default'}
onClick={() => handleGroupToggle(group.groupId, 'startEndRace')}
style={{ marginRight: 8 }}
/>
</div>
</Grid>
);
})}
</Grid>
</Grid>
<Grid item xs={12}>
<Button type="submit" variant="contained" color="primary">
Speichern
</Button>
</Grid>
</Grid>
</form>
</Container>
);
}
// components/Terminal.js
import { useState, useEffect, useRef } from 'react';
import io from 'socket.io-client';
import axios from 'axios';
import { Box, Button, Typography } from '@mui/material';
const Terminal = () => {
const [output, setOutput] = useState([]);
const [socket, setSocket] = useState(null);
const terminalEndRef = useRef(null);
const terminalRef = useRef(null);
const [autoScroll, setAutoScroll] = useState(true);
useEffect(() => {
axios.get('/api/socket').finally(() => {
const socket = io();
setSocket(socket);
socket.on('python-output', (data) => {
setOutput((prevOutput) => [...prevOutput, data]);
});
return () => {
socket.disconnect();
};
});
}, []);
useEffect(() => {
if (autoScroll) {
terminalEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [output, autoScroll]);
const startPythonScript = () => {
if (socket) {
axios.get('/api/process?action=start');
}
};
const stopPythonScript = () => {
if (socket) {
axios.get('/api/process?action=stop');
}
};
useEffect(() => {
const handleKeyDown = (event) => {
if (event.ctrlKey && event.key === 'c') {
stopPythonScript();
}
};
const handleScroll = () => {
if (terminalRef.current) {
const { scrollTop, scrollHeight, clientHeight } = terminalRef.current;
if (scrollTop < scrollHeight - (clientHeight * 2)) {
setAutoScroll(false);
}
else {
setAutoScroll(true);
}
}
};
window.addEventListener('keydown', handleKeyDown);
if (terminalRef.current) {
terminalRef.current.addEventListener('scroll', handleScroll);
}
return () => {
window.removeEventListener('keydown', handleKeyDown);
if (terminalRef.current) {
terminalRef.current.removeEventListener('scroll', handleScroll);
}
};
}, [socket]);
return (
<Box sx={{ display: 'flex', flexDirection: 'column', backgroundColor: 'black', color: 'white', border: 1, borderColor: '#333', borderRadius: 1, width: '100%', height: 400 }}>
<Box sx={{ padding: 1, backgroundColor: '#333', borderBottom: 1, borderColor: '#444', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Button variant="contained" onClick={startPythonScript}>Start Python Script</Button>
<Button variant="contained" onClick={stopPythonScript}>Stop Python Script</Button>
</Box>
<Box ref={terminalRef} sx={{ flexGrow: 1, padding: 1, overflowY: 'auto', fontFamily: 'monospace' }}>
{output.map((line, index) => (
<Typography key={index} sx={{ margin: 0, whiteSpace: 'pre-wrap' }}>{line}</Typography>
))}
<div ref={terminalEndRef} />
</Box>
</Box>
);
};
export default Terminal;
-- phpMyAdmin SQL Dump
-- version 5.2.1
-- https://www.phpmyadmin.net/
--
-- Host: 127.0.0.1
-- Erstellungszeit: 27. Aug 2024 um 23:22
-- Server-Version: 10.4.28-MariaDB
-- PHP-Version: 8.2.4
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Datenbank: `event_management`
--
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `events`
--
CREATE TABLE `events` (
`eventId` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`location` varchar(255) DEFAULT NULL,
`website` varchar(255) DEFAULT NULL,
`description` text DEFAULT NULL,
`startTime` datetime DEFAULT NULL,
`endTime` datetime DEFAULT NULL,
`createdAt` timestamp NOT NULL DEFAULT current_timestamp(),
`updatedAt` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `events`
--
INSERT INTO `events` (`eventId`, `name`, `location`, `website`, `description`, `startTime`, `endTime`, `createdAt`, `updatedAt`) VALUES
(1, '24h Rennen', 'Berlin', 'https://www.veranstaltung1.de', '', '2024-08-12 11:00:00', '2024-08-13 11:00:00', '2024-06-10 10:14:14', '2024-08-18 22:00:44'),
(2, '6h Rennen', 'Hamburg', 'https://www.veranstaltung2.de', '', NULL, NULL, '2024-06-10 10:14:14', '2024-08-18 20:01:00'),
(3, 'Kids, Jugend Ride', 'München', 'https://www.veranstaltung3.de', '', NULL, NULL, '2024-06-10 10:14:14', '2024-08-18 20:02:59'),
(16, 'Laufrad Ride', '', '', '', NULL, NULL, '2024-08-18 20:03:21', '2024-08-18 20:03:21');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `groups`
--
CREATE TABLE `groups` (
`groupId` int(11) NOT NULL,
`name` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `groups`
--
INSERT INTO `groups` (`groupId`, `name`) VALUES
(1, 'Herren Elite'),
(2, 'Frauen Elite');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `groupsmn`
--
CREATE TABLE `groupsmn` (
`id` int(11) NOT NULL,
`eventId` int(11) NOT NULL,
`groupId` int(11) NOT NULL,
`raceType` int(11) NOT NULL,
`value` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `groupsmn`
--
INSERT INTO `groupsmn` (`id`, `eventId`, `groupId`, `raceType`, `value`) VALUES
(64, 2, 1, 2, 21600),
(65, 2, 2, 2, 21600),
(67, 16, 1, 1, 10),
(68, 16, 2, 1, 10),
(71, 3, 1, 2, 7200),
(72, 3, 2, 2, 7200),
(75, 1, 1, 2, 86400),
(76, 1, 2, 2, 86400);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `participants`
--
CREATE TABLE `participants` (
`participantsId` int(11) NOT NULL,
`eventId` int(11) NOT NULL,
`startNumber` int(11) DEFAULT NULL,
`last_name` varchar(100) DEFAULT NULL,
`first_name` varchar(100) DEFAULT NULL,
`title` varchar(15) DEFAULT NULL,
`birth_year` int(11) DEFAULT NULL,
`birth_date` date DEFAULT NULL,
`gender` varchar(2) DEFAULT NULL,
`nationality` varchar(50) DEFAULT NULL,
`competition` int(11) DEFAULT NULL,
`club` varchar(100) DEFAULT NULL,
`license` varchar(25) DEFAULT NULL,
`status` int(11) DEFAULT NULL,
`comment` text DEFAULT NULL,
`transponder1` varchar(40) DEFAULT NULL,
`transponder2` varchar(40) DEFAULT NULL,
`reg_number` int(11) DEFAULT NULL,
`street` varchar(100) DEFAULT NULL,
`postal_code` varchar(10) DEFAULT NULL,
`city` varchar(50) DEFAULT NULL,
`state` varchar(3) DEFAULT NULL,
`country` varchar(50) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`phone` varchar(50) DEFAULT NULL,
`mobile` varchar(255) DEFAULT NULL,
`account_holder` varchar(50) DEFAULT NULL,
`account_number` varchar(12) DEFAULT NULL,
`bank_code` varchar(12) DEFAULT NULL,
`bank_name` varchar(50) DEFAULT NULL,
`iban` varchar(36) DEFAULT NULL,
`bic` varchar(11) DEFAULT NULL,
`sepa_mandate` varchar(35) DEFAULT NULL,
`createdAt` timestamp NOT NULL DEFAULT current_timestamp(),
`updatedAt` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `participants`
--
INSERT INTO `participants` (`participantsId`, `eventId`, `startNumber`, `last_name`, `first_name`, `title`, `birth_year`, `birth_date`, `gender`, `nationality`, `competition`, `club`, `license`, `status`, `comment`, `transponder1`, `transponder2`, `reg_number`, `street`, `postal_code`, `city`, `state`, `country`, `email`, `phone`, `mobile`, `account_holder`, `account_number`, `bank_code`, `bank_name`, `iban`, `bic`, `sepa_mandate`, `createdAt`, `updatedAt`) VALUES
(1, 1, 10, 'Küpper', 'Jan', NULL, 1998, '1998-10-31', 'M', NULL, 1, NULL, NULL, NULL, NULL, '400017B5E758E1DE023F4DCDC553D8D743E05F75', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'jan.kuepper1998@googlemail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2024-06-14 13:06:00', '2024-08-11 09:00:22'),
(3, 1, 11, 'Mustermann', 'Max', NULL, NULL, NULL, NULL, NULL, 1, NULL, NULL, NULL, NULL, '4000FEFD8EFB8FA80237E02626872E6B03DD3928', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'max.mustermann@mail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2024-06-14 14:09:27', '2024-07-22 10:47:14');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `tracking`
--
CREATE TABLE `tracking` (
`trackingId` int(11) NOT NULL,
`transponder` varchar(40) DEFAULT NULL,
`eventId` int(11) NOT NULL,
`time` float DEFAULT NULL,
`hits` int(11) DEFAULT NULL,
`timingPoint` varchar(100) DEFAULT NULL,
`decoderId` varchar(50) DEFAULT NULL,
`utcTime` datetime DEFAULT NULL,
`createdAt` timestamp NOT NULL DEFAULT current_timestamp(),
`updatedAt` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Daten für Tabelle `tracking`
--
INSERT INTO `tracking` (`trackingId`, `transponder`, `eventId`, `time`, `hits`, `timingPoint`, `decoderId`, `utcTime`, `createdAt`, `updatedAt`) VALUES
(110, '400095273856E472028396604E945EBE42B0DF60', 1, 2049.12, 5, 'Finish Line', '1', '2024-08-12 13:34:57', '2024-07-22 10:44:31', '2024-08-19 19:42:12'),
(111, '4000FEFD8EFB8FA80237E02626872E6B03DD3928', 1, 2232.7, 5, 'Finish Line', '1', '2024-08-12 13:34:57', '2024-07-22 10:44:31', '2024-08-19 19:42:12'),
(112, '400095273856E472028396604E945EBE42B0DF60', 1, 300102, 7, 'Finish Line', '1', '2024-08-12 13:34:57', '2024-07-22 10:44:36', '2024-08-19 19:42:12'),
(113, '4000FEFD8EFB8FA80237E02626872E6B03DD3928', 1, 350233, 8, 'Finish Line', '1', '2024-08-12 13:34:57', '2024-07-22 10:44:37', '2024-08-19 19:42:12'),
(114, '400095273856E472028396604E945EBE42B0DF60', 1, 600102, 7, 'Finish Line', '1', '2024-08-12 13:34:57', '2024-07-22 10:44:36', '2024-08-19 19:42:12'),
(115, '4000FEFD8EFB8FA80237E02626872E6B03DD3928', 1, 750233, 8, 'Finish Line', '1', '2024-08-12 13:34:57', '2024-07-22 10:44:37', '2024-08-19 19:42:12'),
(812, '400017B5E758E1DE023F4DCDC553D8D743E05F75', 1, 47.4715, 1, 'Finish Line', '1', '2024-08-12 13:34:56', '2024-08-12 11:34:57', '2024-08-19 19:42:12'),
(813, '400017B5E758E1DE023F4DCDC553D8D743E05F75', 1, 1055.3, 2, 'Finish Line', '1', '2024-08-12 13:34:57', '2024-08-12 11:34:58', '2024-08-19 19:42:12'),
(814, '400017B5E758E1DE023F4DCDC553D8D743E05F75', 1, 2062.16, 2, 'Finish Line', '1', '2024-08-12 13:34:58', '2024-08-12 11:34:59', '2024-08-19 19:42:12'),
(815, '400017B5E758E1DE023F4DCDC553D8D743E05F75', 1, 3068.34, 1, 'Finish Line', '1', '2024-08-12 13:34:59', '2024-08-12 11:35:00', '2024-08-19 19:42:12'),
(816, '400017B5E758E1DE023F4DCDC553D8D743E05F75', 1, 4074.2, 1, 'Finish Line', '1', '2024-08-12 13:35:00', '2024-08-12 11:35:01', '2024-08-19 19:42:12'),
(817, '400017B5E758E1DE023F4DCDC553D8D743E05F75', 1, 5079.34, 2, 'Finish Line', '1', '2024-08-12 13:35:01', '2024-08-12 11:35:02', '2024-08-19 19:42:12');
--
-- Indizes der exportierten Tabellen
--
--
-- Indizes für die Tabelle `events`
--
ALTER TABLE `events`
ADD PRIMARY KEY (`eventId`);
--
-- Indizes für die Tabelle `groups`
--
ALTER TABLE `groups`
ADD PRIMARY KEY (`groupId`);
--
-- Indizes für die Tabelle `groupsmn`
--
ALTER TABLE `groupsmn`
ADD PRIMARY KEY (`id`);
--
-- Indizes für die Tabelle `participants`
--
ALTER TABLE `participants`
ADD PRIMARY KEY (`participantsId`);
--
-- Indizes für die Tabelle `tracking`
--
ALTER TABLE `tracking`
ADD PRIMARY KEY (`trackingId`);
--
-- AUTO_INCREMENT für exportierte Tabellen
--
--
-- AUTO_INCREMENT für Tabelle `events`
--
ALTER TABLE `events`
MODIFY `eventId` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=17;
--
-- AUTO_INCREMENT für Tabelle `groups`
--
ALTER TABLE `groups`
MODIFY `groupId` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
--
-- AUTO_INCREMENT für Tabelle `groupsmn`
--
ALTER TABLE `groupsmn`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=77;
--
-- AUTO_INCREMENT für Tabelle `participants`
--
ALTER TABLE `participants`
MODIFY `participantsId` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12;
--
-- AUTO_INCREMENT für Tabelle `tracking`
--
ALTER TABLE `tracking`
MODIFY `trackingId` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=818;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
Source diff could not be displayed: it is too large. Options to address this: view the blob.
{
"name": "zeitnahme",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.16.6",
"@mui/material": "^5.15.19",
"autoprefixer": "^10.4.19",
"axios": "^1.7.2",
"body-parser": "^1.20.2",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"mysql2": "^3.10.0",
"next": "14.2.3",
"react": "^18",
"react-dom": "^18",
"react-router-dom": "^6.26.2",
"sequelize": "^6.37.3",
"socket.io": "^4.7.5",
"socket.io-client": "^4.7.5"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5"
}
}
import '../styles/globals.css';
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
\ No newline at end of file
import Terminal from '../../components/Terminal';
import { useState, useEffect } from 'react';
import { Container, Typography, TextField, Button, Grid, FormControl, InputLabel, Select, MenuItem, RadioGroup, FormControlLabel, Radio, CircularProgress } from '@mui/material';
import axios from 'axios';
export default function Controller() {
const [config, setConfig] = useState({
timing_point: '',
decoder_id: '',
event_id: '',
event_name: '',
timedelta: '',
// host: '',
// user: '',
// password: '',
// database: '',
connection_type: 'com_port',
connection_value: '',
});
const [events, setEvents] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
axios.get('/api/config').then((response) => {
setConfig(response.data);
});
axios.get('/api/events')
.then(response => setEvents(response.data))
.catch(error => console.error('Error loading events:', error));
}, []);
const handleChange = (e) => {
const { name, value } = e.target;
setConfig((prevConfig) => ({ ...prevConfig, [name]: value }));
};
const handleSave = async () => {
setLoading(true);
await axios.post('/api/config', config).then(() => {
});
setLoading(false);
};
const handleEventChange = (e) => {
const selectedEvent = events.find(event => event.eventId === e.target.value);
setConfig((prevConfig) => ({
...prevConfig,
event_id: selectedEvent?.eventId || '',
event_name: selectedEvent?.name || ''
}));
};
const handleImport = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const importedConfig = JSON.parse(e.target.result);
setConfig(importedConfig);
};
reader.readAsText(file);
}
};
const handleExport = () => {
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(config, null, 2));
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", "config.json");
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
};
return (
<Container>
<Typography variant="h4" component="h4" gutterBottom>
Controller
</Typography>
<Terminal />
<Grid container spacing={2} sx={{ marginTop: 2 }}>
<Grid item xs={12}>
<TextField
label="Timing Point"
name="timing_point"
value={config.timing_point}
onChange={handleChange}
fullWidth
/>
</Grid>
<Grid item xs={12}>
<TextField
label="Decoder ID"
name="decoder_id"
value={config.decoder_id}
onChange={handleChange}
fullWidth
/>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth>
<InputLabel>Event</InputLabel>
<Select
value={config.event_id}
onChange={handleEventChange}
>
{events.map((event) => (
<MenuItem key={event.eventId} value={event.eventId}>
{event.name}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<TextField
label="Timedelta"
name="timedelta"
value={config.timedelta}
onChange={handleChange}
fullWidth
/>
</Grid>
{/* <Grid item xs={12}>
<TextField
label="DB Host"
name="host"
value={config.host}
onChange={handleChange}
fullWidth
/>
</Grid>
<Grid item xs={12}>
<TextField
label="DB User"
name="user"
value={config.user}
onChange={handleChange}
fullWidth
/>
</Grid>
<Grid item xs={12}>
<TextField
label="DB Password"
name="password"
value={config.password}
onChange={handleChange}
fullWidth
type="password"
/>
</Grid>
<Grid item xs={12}>
<TextField
label="DB Name"
name="database"
value={config.database}
onChange={handleChange}
fullWidth
/>
</Grid> */}
<Grid item xs={12}>
<FormControl component="fieldset">
<RadioGroup
name="connection_type"
value={config.connection_type}
onChange={handleChange}
>
<FormControlLabel
value="com_port"
control={<Radio />}
label="Com Port"
/>
<FormControlLabel
value="network"
control={<Radio />}
label="Network"
/>
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12}>
<TextField
label="Connection Value"
name="connection_value"
value={config.connection_value}
onChange={handleChange}
fullWidth
/>
</Grid>
<Grid item xs={12}>
<Button
variant="contained"
color="primary"
onClick={handleSave}
disabled={loading}
>
{loading ? <CircularProgress size={24} /> : 'Speichern'}
</Button>
<Button
variant="contained"
component="label"
sx={{ marginLeft: 2 }}
>
Importieren
<input
type="file"
hidden
accept=".json"
onChange={handleImport}
/>
</Button>
<Button variant="contained" onClick={handleExport} sx={{ marginLeft: 2 }}>
Exportieren
</Button>
</Grid>
</Grid>
</Container>
);
}
import { Container, Tabs, Tab, IconButton, Box } from '@mui/material';
import { Home } from '@mui/icons-material';
import { useRouter } from 'next/router';
import { useState } from 'react';
import EventInformation from './information';
import ParticipantsPage from './participants';
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div role="tabpanel" hidden={value !== index} {...other}>
{value === index && <Box p={3}>{children}</Box>}
</div>
);
}
export default function EventDetails() {
const [tabIndex, setTabIndex] = useState(0);
const router = useRouter();
const handleTabChange = (event, newValue) => {
setTabIndex(newValue);
};
const goHome = () => {
router.push('/admin/');
};
return (
<Container>
<Box display="flex" alignItems="center">
<IconButton onClick={goHome} sx={{ mr: 2 }}>
<Home />
</IconButton>
<Tabs value={tabIndex} onChange={handleTabChange}>
<Tab label="Informationen" />
<Tab label="Teilnehmer" />
</Tabs>
</Box>
<TabPanel value={tabIndex} index={0}>
<EventInformation />
</TabPanel>
<TabPanel value={tabIndex} index={1}>
<ParticipantsPage />
</TabPanel>
</Container>
);
}
import EventForm from '../../../../components/EventForm';
import { useRouter } from 'next/router';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { CircularProgress, Container, Typography } from '@mui/material';
const EventInformation = () => {
const router = useRouter();
const { id } = router.query;
const [eventData, setEventData] = useState({
name: '',
location: '',
website: '',
description: '',
startTime:'',
endTime: '',
groups: {
roundRace: [],
timeRace: [],
startEndRace: []
}
});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchEventData = async () => {
try {
const eventResponse = axios.get(`/api/events/${id}`);
const groupsResponse = axios.get(`/api/events/${id}/groupMn`);
const [eventRes, groupsRes] = await Promise.all([eventResponse, groupsResponse]);
const formattedGroups = {
roundRace: [],
timeRace: [],
startEndRace: []
};
groupsRes.data.forEach(groupMn => {
const formattedGroup = {
groupId: groupMn.groupId,
value: groupMn.value
};
switch (groupMn.raceType) {
case 1:
formattedGroups.roundRace.push(formattedGroup);
break;
case 2:
formattedGroups.timeRace.push(formattedGroup);
break;
case 3:
formattedGroups.startEndRace.push(formattedGroup);
break;
default:
console.warn(`Unbekannter raceType: ${groupMn.raceType}`);
break;
}
});
setEventData({
...eventRes.data,
groups: formattedGroups
});
} catch (error) {
console.error('Error fetching event data or groups:', error);
} finally {
setLoading(false);
}
};
if (id) {
fetchEventData();
}
}, [id]);
const handleUpdate = async (formData) => {
try {
await axios.put(`/api/events/${id}`, formData);
router.push('/admin');
} catch (error) {
console.error('Error updating event:', error);
}
};
if (loading) {
return (
<Container>
<CircularProgress />
</Container>
);
}
return (
<div>
{eventData ? (
<EventForm initialValues={eventData} onSubmit={handleUpdate} />
) : (
<Container>
<CircularProgress />
</Container>
)}
</div>
);
};
export default EventInformation;
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import EditableTable from '../../../../components/EditableTable';
import axios from 'axios';
import { CircularProgress, Container, Typography } from '@mui/material';
const ParticipantsPage = () => {
const router = useRouter();
const { id } = router.query;
const [participants, setParticipants] = useState([]);
const [event, setEvent] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchParticipants = async () => {
try {
const response = await axios.get(`/api/events/${id}/participants`);
setParticipants(response.data);
setLoading(false);
} catch (error) {
console.error('Fehler beim Laden der Teilnehmer:', error);
setLoading(false);
}
};
if (id) {
fetchParticipants();
}
const fetchEventData = async () => {
try {
const response = await axios.get(`/api/events/${id}`);
setEvent(response.data);
setLoading(false);
} catch (error) {
console.error('Error fetching event data:', error);
}
};
if (id) {
fetchEventData();
}
}, [id]);
if (loading) {
return (
<Container>
<Typography variant="h4" component="h4" gutterBottom>
Teilnehmer {event ? event.name : `Veranstaltung ${id}`}
</Typography>
<CircularProgress />
</Container>
);
}
return (
<Container>
<Typography variant="h4" component="h4" gutterBottom>
Teilnehmer {event ? event.name : `Veranstaltung ${id}`}
</Typography>
<EditableTable eventId={id} initialParticipants={participants} />
</Container>
);
};
export default ParticipantsPage;
\ No newline at end of file
import EventForm from '../../../components/EventForm';
import axios from 'axios';
import { useRouter } from 'next/router';
export default function NewEvent() {
const router = useRouter();
const handleSubmit = async (formData) => {
try {
await axios.post('/api/events', formData);
router.push('/admin');
} catch (error) {
console.error('Error creating event:', error);
}
};
return <EventForm onSubmit={handleSubmit} />;
}
import Link from 'next/link';
import { Container, Typography, Grid, Fab, Button, Box } from '@mui/material';
import { useEffect, useState } from 'react';
import axios from 'axios';
export default function Admin() {
const [events, setEvents] = useState([]);
useEffect(() => {
axios.get('/api/events')
.then(response => setEvents(response.data))
.catch(error => console.error('Error loading events:', error));
}, []);
return (
<Container>
<Typography variant="h4" component="h4" gutterBottom>Admin</Typography>
<Box display="flex" justifyContent="flex-end" mb={2}>
<Fab color="primary" aria-label="add" href="/admin/events/new">
+
</Fab>
</Box>
<Grid container spacing={3}>
{events.map(event => (
<Grid item key={event.id} xs={12} sm={6} md={4}>
<Link href={`/admin/events/${event.eventId}`} passHref>
<Button variant="contained" color="primary" fullWidth>{event.name}</Button>
</Link>
</Grid>
))}
</Grid>
</Container>
);
}
import fs from 'fs';
import path from 'path';
const configFilePath = path.resolve(process.cwd(), 'server/config.json');
export default function handler(req, res) {
if (req.method === 'GET') {
if (fs.existsSync(configFilePath)) {
const configData = fs.readFileSync(configFilePath, 'utf-8');
res.status(200).json(JSON.parse(configData));
} else {
res.status(200).json({ timing_point: '', decoder_id: '', event_id: '' });
}
} else if (req.method === 'POST') {
const configData = req.body;
fs.writeFileSync(configFilePath, JSON.stringify(configData, null, 2));
res.status(200).json({ message: 'Config saved successfully' });
} else {
res.status(405).end();
}
}
\ No newline at end of file
import { GroupMn, Group } from '../../../../server/db';
export default async function handler(req, res) {
const { id: eventId } = req.query;
const groupMns = await GroupMn.findAll({
where: { eventId },
include: Group
});
res.status(200).json(groupMns);
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment