Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
## v8.2

* Users are now able to propose corrections to worktimes when they have been marked as for "in review".
* Added project management. Take a look into the README.md for more information.
* Administrators can now change the theme globally within TimeTrack. See more within `README.md`.
* Added `theme_file` and `force_theme` keys to the `app.json` `[general]` section
* Users can choose their own theme, if `force_theme` is set to `false`

## v8.1

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ In step 2, you need to configure the `app.json.sample` within the `api/v1/inc` f
- `db_*`: Set the connection details for your mysql instance
- `app`: If set to true, users will be able to use the TimeTrack mobile application
- `timezone`: Set the timezone of your application, e.g. `Europe/Berlin` or `America/New_York` (default: `UTC`)
- `force_theme`: Force a theme for all users, this disables the feature allowing users to set their own theme.
- `theme_file`: If `force_theme` is true, the specified theme is used (default: `/assets/css/v8.css`)

#### **SMTP section**

Expand Down Expand Up @@ -186,6 +188,15 @@ Users can access their projects within the `Projects` tab.

You can create items for their projects and map worktimes to it. The feature will be reworked in the future.

## Themes

Users can now select their own theme within the `Settings` page. It loads all available themes that reside within the `/assets/css` folder.
Administrators can enforce a theme globally by setting `force_theme` to `true`. If so, only the theme specified within `theme_file` is available.

To upload a new theme, simply place it into the `/assets/css` folder.

The theme the user selected is saved as a cookie, meaning it is only selected on the current device. On mobile or on another device, the user has to set the desired theme again.

## Updates

TimeTrack has to be updated in two ways: database and application.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8.1
8.2
3 changes: 3 additions & 0 deletions api/v1/class/arbeitszeit.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use Arbeitszeit\Events\WorktimeMarkedForReviewEvent;
use Arbeitszeit\Events\WorktimeUnlockedFromReviewEvent;
use Arbeitszeit\Events\FixEasymodeWorktimeEvent;
use Arbeitszeit\Events\WorktimeCorrectionProposedEvent;
/**
* Beinhaltet wesentliche Inhalte, wie Einstellungen, Arbeitszeiten erstellen, etc.
*
Expand Down Expand Up @@ -409,6 +410,8 @@ public function update_worktime($id, $array)
return false;
}

EventDispatcherService::get()->dispatch(new WorktimeCorrectionProposedEvent($_SESSION["username"], (int) $id), WorktimeCorrectionProposedEvent::NAME);
Exceptions::error_rep("Worktime entry with ID '{$id}' updated successfully.");
return true;
} catch (\PDOException $e) {
Exceptions::error_rep("[WORKTIME] Update failed: " . $e->getMessage());
Expand Down
44 changes: 44 additions & 0 deletions api/v1/class/benutzer/benutzer.arbeit.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,50 @@ public function editUserProperties(mixed $username_or_id, string $name, mixed $v
return false;
}
}

public function loadUserTheme(){

$themes = scandir($_SERVER["DOCUMENT_ROOT"] . "/assets/css");
$themes = array_diff($themes, [".", ".."]);
$check = in_array($_COOKIE["theme"], $themes);
if($this->get_app_ini()["general"]["force_theme"] == "true"){
return $this->get_app_ini()["general"]["theme_file"];
}

if(!isset($_COOKIE["theme"]) || !$check){
return "/assets/css/v8.css";
} else {
return "/assets/css/" . $_COOKIE["theme"];
}
}

public function computeUserThemes(){
$themes = scandir($_SERVER["DOCUMENT_ROOT"] . "/assets/css");
$themes = array_diff($themes, [".", ".."]);
$currentTheme = basename($this->loadUserTheme());
foreach($themes as $theme){
if($currentTheme == $theme){
echo "<option name='{$theme}' selected>{$theme}</option>";
} else {
echo "<option name='{$theme}'>{$theme}</option>";
}
}

return true;
}

public function setUserTheme($theme){
setcookie("theme", $theme, time()+60*60*24*30, "/");
return true;
}

public function checkThemeForce(){
if($this->get_app_ini()["general"]["force_theme"] == "true" || $this->get_app_ini()["general"]["force_theme"] == true){
return true;
} else {
return false;
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions api/v1/class/events/loader.events.arbeit.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
require_once dirname(__DIR__, 1) . "/events/worktimes/WorktimeMarkedForReviewEvent.php";
require_once dirname(__DIR__, 1) . "/events/worktimes/WorktimeAddedEvent.php";
require_once dirname(__DIR__, 1) . "/events/worktimes/WorktimeDeletedEvent.php";
require_once dirname(__DIR__, 1) . "/events/worktimes/WorktimeCorrectionProposedEvent.php";

require_once dirname(__DIR__, 1) . "/events/notifications/CreatedNotificationEvent.php";
require_once dirname(__DIR__, 1) . "/events/notifications/DeletedNotificationEvent.php";
Expand Down
27 changes: 27 additions & 0 deletions api/v1/class/events/worktimes/WorktimeCorrectionProposedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
namespace Arbeitszeit\Events;

use Symfony\Contracts\EventDispatcher\Event;

class WorktimeCorrectionProposedEvent extends Event
{
public const NAME = 'worktimes.correction_proposed';

private string $username;
private int $id;
public function __construct(string $username, int $id){
$this->username = $username;
$this->id = $id;
}

public function getUsername(): string
{
return $this->username;
}

public function getId(): int
{
return $this->id;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"subject1": "Deine Arbeitszeit muss geprüft werden!",
"suject2": "Die Prüfung deiner Arbeitszeit wurde aufgehoben!",
"greetings": "Hallo",
"message1": "dein Vorgesetzter hat deine Arbeitszeit auf Prüfung gesetzt. Die betroffene Arbeitszeit ist unten aufgelistet",
"message1": "dein Vorgesetzter hat deine Arbeitszeit auf Prüfung gesetzt. Die betroffene Arbeitszeit ist unten aufgelistet. Du kannst eine Änderung vorschlagen.",
"message2": "dein Vorgesetzter hat die Prüfung deiner Arbeitszeit aufgehoben. Die betroffene Arbeitszeit ist unten aufgelistet",
"id": "ID",
"username": "Benutzername",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"subject1": "Your working hours need to be reviewed!",
"suject2": "Your working hours have been unchecked!",
"greetings": "Hello",
"message1": "Your supervisor has put your working hours under review. The affected working hours are listed below",
"message1": "Your supervisor has put your working hours under review. The affected working hours are listed below. You can suggest a change.",
"message2": "Your supervisor has unchecked your working hours. The affected working hours are listed below",
"id": "ID",
"username": "Username",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"subject1": "Uw werktijden moeten worden gecontroleerd!",
"suject2": "De controle op uw werktijden is geannuleerd!",
"greetings": "Hallo",
"message1": "uw manager heeft uw werktijden beoordeeld. De betreffende werktijden staan ​​hieronder vermeld",
"message1": "uw manager heeft uw werktijden beoordeeld. De betreffende werktijden staan ​​hieronder vermeld. U kunt een wijziging voorstellen.",
"message2": "Uw manager heeft de beoordeling van uw werktijden geannuleerd. De betreffende werktijden worden hieronder vermeld",
"id": "ID",
"username": "Gebruikersnaam",
Expand Down
4 changes: 3 additions & 1 deletion api/v1/inc/app.json.sample
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"debug": "false",
"auto_update": "false",
"app": "false",
"timezone": "UTC"
"timezone": "UTC",
"theme_file": "/assets/css/v8.css",
"force_theme": "false"
},
"mysql": {
"db_host": "localhost",
Expand Down
207 changes: 207 additions & 0 deletions assets/css/cyberpunk.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
:root {
--primary: #ff2e92;
--secondary: #00fff7;
--bg: #0a0a0f;
--text: #f2f2f2;
--card: #141421;
--radius: 14px;
--font-main: "Orbitron", sans-serif;
--font-mono: "JetBrains Mono", monospace;
}

html, body {
margin: 0;
padding: 0;
font-family: var(--font-main);
background: radial-gradient(circle at top left, #0f0f1a, #0a0a0f 80%);
color: var(--text);
min-height: 100vh;
overflow-x: hidden;
animation: fadeBg 10s ease-in-out infinite alternate;
}

@keyframes fadeBg {
0% { background: radial-gradient(circle at top left, #0f0f1a, #0a0a0f 80%); }
100% { background: radial-gradient(circle at top right, #1a0f1f, #0a0a0f 80%); }
}

a {
color: var(--secondary);
text-decoration: none;
transition: all 0.3s ease;
}
a:hover {
color: var(--primary);
text-shadow: 0 0 8px var(--primary);
}

h1, h2, h3 {
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--secondary);
text-shadow: 0 0 12px rgba(0,255,247,0.6);
animation: fadeSlideUp 0.6s ease-out both;
}

input, button, textarea {
border-radius: var(--radius);
border: none;
padding: 0.8rem 1rem;
margin-top: 0.5rem;
font-family: var(--font-main);
background: #1a1a2a;
color: var(--text);
transition: all 0.3s ease;
}
input:focus, textarea:focus {
outline: none;
box-shadow: 0 0 12px var(--secondary);
}

button {
background: var(--primary);
color: black;
font-weight: bold;
cursor: pointer;
letter-spacing: 1px;
transition: all 0.3s ease, transform 0.2s ease;
text-shadow: 0 0 4px rgba(255,255,255,0.2);
}
button:hover {
background: var(--secondary);
color: #0a0a0f;
transform: translateY(-2px) scale(1.03);
box-shadow: 0 0 15px var(--secondary);
}

.card {
background: var(--card);
padding: 2rem;
border-radius: var(--radius);
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 0 20px rgba(0,0,0,0.6), 0 0 12px rgba(255,46,146,0.25);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 0 25px rgba(0,255,247,0.3);
}
.card h2 {
color: var(--primary);
}

footer {
font-size: 0.85rem;
text-align: center;
padding: 2rem;
color: #aaa;
border-top: 1px solid rgba(255,255,255,0.1);
background: rgba(20,20,30,0.7);
backdrop-filter: blur(8px);
animation: glowPulse 4s infinite;
}

.topnav {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
background: rgba(20,20,40,0.8);
padding: 0.75rem 1.5rem;
border-bottom: 2px solid var(--primary);
font-size: 0.95rem;
backdrop-filter: blur(6px);
animation: slideGlow 6s linear infinite;
}
.topnav a {
color: var(--text);
margin-right: 1rem;
padding: 0.3rem 0.5rem;
}
.topnav a:hover {
color: var(--primary);
text-shadow: 0 0 8px var(--primary);
}

.user-label {
font-weight: bold;
font-family: var(--font-mono);
color: var(--secondary);
text-shadow: 0 0 8px var(--secondary);
}

.nav-version {
font-size: 0.8rem;
color: var(--primary);
font-family: var(--font-mono);
margin-left: 1rem;
}

/* Status Messages */
.status-message {
border-left: 4px solid var(--primary);
padding: 1rem 1.5rem;
margin-bottom: 1.5rem;
border-radius: var(--radius);
font-family: var(--font-mono);
position: relative;
background: rgba(255,255,255,0.05);
color: var(--text);
animation: fadeSlideUp 0.4s ease;
}
.status-message.error { border-left-color: #ff5555; color: #ff9999; }
.status-message.warn { border-left-color: #ffdd57; color: #ffeaa7; }
.status-message.info { border-left-color: var(--secondary); }

/* Tables */
.v8-table {
width: 100%;
border-collapse: collapse;
font-size: 0.95rem;
font-family: var(--font-mono);
background-color: #151522;
border: 1px solid rgba(255,255,255,0.1);
}
.v8-table th,
.v8-table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid #222;
}
.v8-table thead {
background-color: #1f1f2f;
color: var(--primary);
border-bottom: 2px solid var(--primary);
}
.v8-table tr:hover {
background: rgba(255,46,146,0.07);
}

/* Log Output */
.log-output {
font-family: var(--font-mono);
font-size: 0.85rem;
background: #111;
padding: 1rem;
border-radius: var(--radius);
border: 1px solid rgba(255,255,255,0.08);
color: var(--secondary);
max-height: 300px;
overflow-y: auto;
}
.log-output .error { color: #ff2e92; font-weight: bold; }
.log-output .warn { color: #ffdd57; }
.log-output .info { color: var(--secondary); }

/* Animations */
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes glowPulse {
0%,100% { box-shadow: 0 0 0px rgba(255,46,146,0); }
50% { box-shadow: 0 0 12px rgba(255,46,146,0.4); }
}
@keyframes slideGlow {
0% { border-image: linear-gradient(90deg, var(--primary), var(--secondary)) 1; }
100% { border-image: linear-gradient(90deg, var(--secondary), var(--primary)) 1; }
}
Loading
Loading