13 Commits
Author SHA1 Message Date
TorstenHettstedt fb91d1121e Merge pull request 'refactor/js-repositories' (#20) from refactor/js-repositories into master
Reviewed-on: #20
2021-12-15 20:41:24 +01:00
TorstenHettstedt e10b72e086 Merge pull request 'bugfix/rest-api-phpstan' (#21) from bugfix/rest-api-phpstan into master
Reviewed-on: #21
2021-12-15 19:50:35 +01:00
Torstem_JetBrain aae4764f5e Refactor: Probleme für PHP-Storm beseitigen 2021-12-15 19:48:20 +01:00
Torstem_JetBrain 371425f3c5 Bugfix: PHPStan kommt damit nicht klar 2021-12-15 19:47:41 +01:00
Torstem_JetBrain c3a1953cc9 Refactor: Anzeige der verschiedenen Zeiträume verbessern 2021-12-15 18:50:32 +01:00
Torstem_JetBrain 66022e42df Refactor: Aufteilung der Darstellung der Einträge 2021-12-15 18:21:29 +01:00
Torstem_JetBrain 907ce88ed1 fix: Export-Objekte müssen konstant sein 2021-12-15 18:16:48 +01:00
Torstem_JetBrain 45650a0f9a refactor: Abfrage in ein eigenes Modul ausgelagert. 2021-12-15 17:47:02 +01:00
TorstenHettstedt 3dd0f451fc Merge pull request 'Deployment.' (#14) from deployment into master
Reviewed-on: #14
2021-04-19 11:14:28 +02:00
Torstem_JetBrain 46313cc290 Die Installation der PHP-Abhängigkeiten hinzugefügt. 2021-04-19 11:11:06 +02:00
Torstem_JetBrain 65195e702e Für den Anfang wird eine einfache Bash-Datei zum Deployment benutzt. 2021-04-16 21:58:54 +02:00
Torstem_JetBrain ec22f42623 Docker-Composer-Datei wurden um die Oberfläche erweitert. 2021-04-16 21:47:23 +02:00
Torstem_JetBrain b43b6382d7 Font-Dateien wurden nicht mit gesichert. 2021-04-16 21:46:07 +02:00
29 changed files with 78888 additions and 285 deletions
+9 -13
View File
@@ -31,7 +31,6 @@ class ErrorHandlerTest extends Unit
protected function _before(): void protected function _before(): void
{ {
parent::_before(); parent::_before();
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->app = $this->makeEmpty(App::class, [ $this->app = $this->makeEmpty(App::class, [
'getResponseFactory' => $this->makeEmpty(ResponseFactoryInterface::class, [ 'getResponseFactory' => $this->makeEmpty(ResponseFactoryInterface::class, [
'createResponse' => $this->makeEmpty(ResponseInterface::class, [ 'createResponse' => $this->makeEmpty(ResponseInterface::class, [
@@ -44,15 +43,18 @@ class ErrorHandlerTest extends Unit
]), ]),
]), ]),
]); ]);
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->request = $this->makeEmpty(ServerRequestInterface::class, [ $this->request = $this->makeEmpty(ServerRequestInterface::class, [
'getAttribute' => function ($name) { 'getAttribute' => function ($name) {
return match ($name) { /** @noinspection PhpSwitchCanBeReplacedWithMatchExpressionInspection */
RouteContext::ROUTE_PARSER => $this->makeEmpty(RouteParserInterface::class), switch ($name) {
RouteContext::ROUTING_RESULTS => $this->makeEmpty(RoutingResults::class), case RouteContext::ROUTE_PARSER:
default => null, return $this->makeEmpty(RouteParserInterface::class);
}; case RouteContext::ROUTING_RESULTS:
return $this->makeEmpty(RoutingResults::class);
default:
return null;
} }
},
]); ]);
} }
@@ -61,13 +63,11 @@ class ErrorHandlerTest extends Unit
*/ */
public function testUseWithLogger(): void public function testUseWithLogger(): void
{ {
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->exception = $this->make(Exception::class, [ $this->exception = $this->make(Exception::class, [
'message' => '', 'message' => '',
'code' => 400, 'code' => 400,
'file' => '/path(to/file', 'file' => '/path(to/file',
]); ]);
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->logger = $this->makeEmpty(LoggerInterface::class, [ $this->logger = $this->makeEmpty(LoggerInterface::class, [
'error' => Expected::once(), 'error' => Expected::once(),
]); ]);
@@ -81,14 +81,12 @@ class ErrorHandlerTest extends Unit
*/ */
public function testUseWithHttpException(): void public function testUseWithHttpException(): void
{ {
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->exception = $this->make(HttpException::class, [ $this->exception = $this->make(HttpException::class, [
'message' => '', 'message' => '',
'code' => 400, 'code' => 400,
'file' => '/path(to/file', 'file' => '/path(to/file',
'getTitle' => Expected::once('The Title'), 'getTitle' => Expected::once('The Title'),
]); ]);
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->logger = $this->makeEmpty(LoggerInterface::class, []); $this->logger = $this->makeEmpty(LoggerInterface::class, []);
$middleWare = new ErrorHandler($this->app); $middleWare = new ErrorHandler($this->app);
@@ -101,13 +99,11 @@ class ErrorHandlerTest extends Unit
*/ */
public function testUseWithGeneralException(): void public function testUseWithGeneralException(): void
{ {
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->exception = $this->make(Exception::class, [ $this->exception = $this->make(Exception::class, [
'message' => '', 'message' => '',
'code' => 400, 'code' => 400,
'file' => '/path(to/file', 'file' => '/path(to/file',
]); ]);
/** @noinspection PhpFieldAssignmentTypeMismatchInspection */
$this->logger = $this->makeEmpty(LoggerInterface::class, []); $this->logger = $this->makeEmpty(LoggerInterface::class, []);
$middleWare = new ErrorHandler($this->app); $middleWare = new ErrorHandler($this->app);
+6
View File
@@ -24,3 +24,9 @@ services:
volumes: volumes:
- ./api:/var/www - ./api:/var/www
- logs:/var/www/logs - logs:/var/www/logs
ui:
build: ./ui/
ports:
- 80:80
depends_on:
- api
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
install_api_dependence() {
cd api/ || exit 2
composer install
cd ..
}
build_ui() {
cd ui/ || exit 2
npm run build
cd ..
}
test_unit() {
pwd
cd api/ || exit 2
./vendor/bin/codecept run unit
cd ..
}
test_api() {
cd api/ || exit 2
./vendor/bin/codecept run api
cd ..
}
composer_run() {
docker-compose up -d
}
install_api_dependence && build_ui && test_unit && composer_run && test_api
+3
View File
@@ -0,0 +1,3 @@
FROM httpd:2.4
COPY ./public/ /usr/local/apache2/htdocs/
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.
Binary file not shown.
+83
View File
@@ -0,0 +1,83 @@
<script context="module">
const MyFetch = {
'post': async (rest_url, request_body) => {
return MyFetch._fetch(rest_url, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'omit',
headers: {
'Content-Type': 'application/json',
},
body: request_body,
}, {})
},
'put': async (rest_url, http_body) => {
return MyFetch._fetch(rest_url, {
method: 'PUT',
mode: 'cors',
cache: 'no-cache',
credentials: 'omit',
headers: {
'Content-Type': 'application/json',
},
body: http_body,
}, {})
},
'get': async (rest_url, query_parameters) => {
if (query_parameters !== null) {
let filter = []
for (let parameter in query_parameters) {
filter.push(parameter + '=' + query_parameters[parameter])
}
if (filter.length > 0) {
rest_url += '?' + filter.join('&')
}
}
return MyFetch._fetch(rest_url, {})
},
'_fetch': async (rest_url, request_options) => {
// noinspection JSUnresolvedVariable
let response = await fetch(env.API_URL + rest_url, request_options).catch(err => {
console.error(err)
return null
})
if (!response.ok) {
throw new Error('Fail!')
}
return response.json()
}
}
export const WorkingHoursRepository = {
'browse': () => {
return MyFetch.get('/working-hours')
},
'read': date => {
return MyFetch.get('/working-hours/' + date)
},
'add': async (record) => {
console.debug(record);
return MyFetch.post('/working-hours', JSON.stringify(record))
},
'update': async (date, record) => {
console.debug(record);
return MyFetch.put('/working-hours/' + date, JSON.stringify(record))
}
}
export const WorkingHoursViewsRepository = {
'weekly': async () => {
return MyFetch.get('/views/working-hours/weekly')
},
'monthly': async () => {
return MyFetch.get('/views/working-hours/monthly')
},
'yearly': async () => {
return MyFetch.get('/views/working-hours/yearly')
},
}
</script>
+3 -23
View File
@@ -1,31 +1,11 @@
<script> <script>
import Views from "./Views.svelte"; import Views from "./Views.svelte";
export let params; import {WorkingHoursViewsRepository} from "../components/Repositories.svelte";
export let params
params = { params = {
title: 'Monatsansicht', title: 'Monatsansicht',
records: [], promise: WorkingHoursViewsRepository.monthly()
isLoading: true
} }
const View = {
'browse': () => {
fetch(env.API_URL + '/views/working-hours/monthly')
.then(res => {
if (!res.ok) {
throw new Error('Fail!');
}
return res.json();
})
.then(data => {
params.records = data;
params.isLoading = false;
})
.catch(err => {
params.isLoading = false;
console.log(err);
});
}
};
View.browse();
</script> </script>
<Views {params} /> <Views {params} />
+11 -3
View File
@@ -1,5 +1,7 @@
<script> <script>
export let params; export let params = {};
let records = []
let isLoading = true
console.log(params) console.log(params)
if (!String.prototype.startsWith) { if (!String.prototype.startsWith) {
String.prototype.startsWith = function(searchString, position) { String.prototype.startsWith = function(searchString, position) {
@@ -9,6 +11,12 @@
} }
$: isNegative = time => time.startsWith('-') ? 'absence-time' : ''; $: isNegative = time => time.startsWith('-') ? 'absence-time' : '';
$: cutMinusChar = time => time.startsWith('-') ? time.substr(1) : time; $: cutMinusChar = time => time.startsWith('-') ? time.substr(1) : time;
const init = async () => await params.promise.then(data => {
records = data
isLoading = false
})
init()
</script> </script>
<svelte:head> <svelte:head>
@@ -20,7 +28,7 @@
<h1>{params.title}</h1> <h1>{params.title}</h1>
</header> </header>
<article> <article>
{#if params.isLoading === true} {#if isLoading === true}
<p>Loading...</p> <p>Loading...</p>
{:else} {:else}
<table> <table>
@@ -39,7 +47,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each params.records as record} {#each records as record}
<tr> <tr>
<td>{record.period}</td> <td>{record.period}</td>
<td>{record.workingDays}</td> <td>{record.workingDays}</td>
+3 -23
View File
@@ -1,31 +1,11 @@
<script> <script>
import Views from "./Views.svelte"; import Views from "./Views.svelte";
export let params; import {WorkingHoursViewsRepository} from "../components/Repositories.svelte";
export let params
params = { params = {
title: 'Wochenansicht', title: 'Wochenansicht',
records: [], promise: WorkingHoursViewsRepository.weekly()
isLoading: true
} }
const View = {
'browse': () => {
fetch(env.API_URL + '/views/working-hours/weekly')
.then(res => {
if (!res.ok) {
throw new Error('Fail!');
}
return res.json();
})
.then(data => {
params.records = data;
params.isLoading = false;
})
.catch(err => {
params.isLoading = false;
console.log(err);
});
}
};
View.browse();
</script> </script>
<Views {params} /> <Views {params} />
+9 -193
View File
@@ -1,216 +1,32 @@
<script> <script>
export let params; import List from "./WorkingHours/List.svelte";
let title = "Bearbeitung Einträge" import Formular from "./WorkingHours/Formular.svelte";
import IconifyIcon from '@iconify/svelte';
import wrenchIcon from '@iconify-icons/oi/wrench'; let TITLE = "Bearbeitung Einträge"
import checkIcon from '@iconify-icons/oi/check';
import xIcon from '@iconify-icons/oi/x';
import documentIcon from '@iconify-icons/oi/document';
let activeRecord = null; let activeRecord = null;
let newRecord = false; let newRecord = false;
params = {
title: 'Einträge',
records: [],
isLoading: true
}
const Records = {
'browse': () => {
// noinspection JSUnresolvedVariable
fetch(env.API_URL + '/working-hours')
.then(res => {
if (!res.ok) {
throw new Error('Fail!');
}
return res.json();
})
.then(data => {
Controller.cancelActiveRecord();
params.records = data;
params.isLoading = false;
})
.catch(err => {
params.isLoading = false;
console.log(err);
});
},
'read': date => {
// noinspection JSUnresolvedVariable
fetch(env.API_URL + '/working-hours/' + date)
.then(res => {
if (!res.ok) {
throw new Error('Fail!');
}
return res.json();
})
.then(data => {
activeRecord = data;
if (date === null) {
Controller.listRecords();
}
})
.catch(err => {
console.log(err);
});
},
'add': async record => {
console.log(record);
// noinspection JSUnresolvedVariable
await fetch(env.API_URL + '/working-hours', {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'omit',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(record)
})
.then(res => {
if (!res.ok) {
throw new Error('Fail!');
}
})
.then(() => {
Controller.listRecords();
})
.catch(err => {
console.log(err);
});
},
'update': async (date, record) => {
console.log(record);
// noinspection JSUnresolvedVariable
await fetch(env.API_URL + '/working-hours/' + date, {
method: 'PUT',
mode: 'cors',
cache: 'no-cache',
credentials: 'omit',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(record)
})
.then(res => {
if (!res.ok) {
throw new Error('Fail!');
}
})
.then(() => {
Controller.listRecords();
})
.catch(err => {
console.log(err);
});
}
};
const Controller = {
'listRecords': () => {
Controller.cancelActiveRecord();
Records.browse();
},
'newRecord': () => {
activeRecord = {workingTime: '00:00:00'};
newRecord = true;
},
'saveRecord': () => {
if (newRecord === true) {
Records.add(activeRecord);
} else {
Records.update(activeRecord.workingDay, activeRecord);
}
},
'selectActiveRecord': record => Records.read(record.workingDay),
'cancelActiveRecord': () => {
activeRecord = null;
newRecord = false;
}
};
const initSite = () => {
Controller.listRecords()
};
initSite()
</script> </script>
<svelte:head> <svelte:head>
<title>{title}</title> <title>{TITLE}</title>
</svelte:head> </svelte:head>
{#if activeRecord === null} {#if activeRecord === null}
<section id="list-records"> <section id="list-records">
<header> <header>
<h1>{title}</h1> <h1>{TITLE}</h1>
</header> </header>
<article> <article>
{#if params.isLoading === true} <List bind:activeRecord={activeRecord} bind:newRecord={newRecord}/>
<p>Loading...</p>
{:else}
<!--suppress HtmlUnknownTarget -->
<form action="/working-hours">
<fieldset>
<button on:click={Controller.newRecord} type="button">Neuer Eintrag
<IconifyIcon icon={documentIcon}/>
</button>
</fieldset>
</form>
<table>
<colgroup>
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<th>Datum</th>
<th>Arbeitsstunden</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{#each params.records as record}
<tr>
<td>{record.workingDay}</td>
<td>{record.workingTime}</td>
<td>
<button on:click={() => Controller.selectActiveRecord(record)} type="button">Bearbeiten
<IconifyIcon icon={wrenchIcon}/>
</button>
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</article> </article>
</section> </section>
{:else} {:else}
<section id="edit-record"> <section id="edit-record">
<header> <header>
<h1>{title}</h1> <h1>{TITLE}</h1>
</header> </header>
<article> <article>
<!--suppress HtmlUnknownTarget --> <Formular bind:activeRecord={activeRecord} bind:newRecord={newRecord}/>
<form action="/working-hours" name="editRecord">
<fieldset name="record-data">
<div>
<label for="workingDay">Arbeitstag</label>
<input bind:value={activeRecord.workingDay} type="date"
id="workingDay" required pattern="\d\{4}-[0-1]\d-[0-3]\d\">
</div>
<div>
<label for="workingTime">Arbeitszeit</label>
<input bind:value={activeRecord.workingTime} placeholder="Arbeitszeit" type="time"
id="workingTime" required pattern="[0-5]\d:[0-5]\d:[0-5]\d">
</div>
</fieldset>
<fieldset name="buttons">
<button on:click={Controller.saveRecord} type="button">Übernehmen
<IconifyIcon icon={checkIcon} color="green"/>
</button>
<button on:click={Controller.cancelActiveRecord} type="button" class="sub-button">Abbrechen
<IconifyIcon icon={xIcon}/>
</button>
</fieldset>
</form>
</article> </article>
</section> </section>
{/if} {/if}
@@ -0,0 +1,49 @@
<script>
import {WorkingHoursRepository} from "../../components/Repositories.svelte"
import IconifyIcon from '@iconify/svelte'
import checkIcon from '@iconify-icons/oi/check'
import xIcon from '@iconify-icons/oi/x'
export let activeRecord = null
export let newRecord = false
const Controller = {
'saveRecord': () => {
if (newRecord === true) {
WorkingHoursRepository.add(activeRecord).then(() => {
Controller.cancelActiveRecord()
});
} else {
WorkingHoursRepository.update(activeRecord.workingDay, activeRecord).then(() => {
Controller.cancelActiveRecord()
});
}
},
'cancelActiveRecord': () => {
activeRecord = null;
newRecord = false;
}
}
</script>
<!--suppress HtmlUnknownTarget -->
<form action="/working-hours" name="editRecord">
<fieldset name="record-data">
<div>
<label for="workingDay">Arbeitstag</label>
<input bind:value={activeRecord.workingDay} type="date"
id="workingDay" required pattern="\d\{4}-[0-1]\d-[0-3]\d\">
</div>
<div>
<label for="workingTime">Arbeitszeit</label>
<input bind:value={activeRecord.workingTime} placeholder="Arbeitszeit" type="time"
id="workingTime" required pattern="[0-5]\d:[0-5]\d:[0-5]\d">
</div>
</fieldset>
<fieldset name="buttons">
<button on:click={Controller.saveRecord} type="button">Übernehmen
<IconifyIcon icon={checkIcon} color="green"/>
</button>
<button on:click={Controller.cancelActiveRecord} type="button" class="sub-button">Abbrechen
<IconifyIcon icon={xIcon}/>
</button>
</fieldset>
</form>
+78
View File
@@ -0,0 +1,78 @@
<script>
import {WorkingHoursRepository} from "../../components/Repositories.svelte"
export let activeRecord = null
export let newRecord = false
let isLoading = true
let records = []
import IconifyIcon from '@iconify/svelte'
import wrenchIcon from '@iconify-icons/oi/wrench'
import documentIcon from '@iconify-icons/oi/document'
const Controller = {
'listRecords': () => {
Controller.cancelActiveRecord();
WorkingHoursRepository.browse().then(data => {
Controller.cancelActiveRecord()
records = data
isLoading = false
});
},
'newRecord': () => {
activeRecord = {workingTime: '00:00:00'};
newRecord = true;
},
'selectActiveRecord': record => WorkingHoursRepository.read(record.workingDay).then(data => {
if (data === null) {
Controller.listRecords()
return
}
activeRecord = data;
}),
'cancelActiveRecord': () => {
activeRecord = null;
newRecord = false;
}
}
Controller.listRecords()
</script>
<!--suppress HtmlUnknownTarget -->
<form action="/working-hours">
<fieldset>
<button on:click={Controller.newRecord} type="button">Neuer Eintrag
<IconifyIcon icon={documentIcon}/>
</button>
</fieldset>
</form>
{#if isLoading === true}
<p>Loading...</p>
{:else}
<table>
<colgroup>
<col/>
<col/>
<col/>
</colgroup>
<thead>
<tr>
<th>Datum</th>
<th>Arbeitsstunden</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{#each records as record}
<tr>
<td>{record.workingDay}</td>
<td>{record.workingTime}</td>
<td>
<button on:click={() => Controller.selectActiveRecord(record)} type="button">Bearbeiten
<IconifyIcon icon={wrenchIcon}/>
</button>
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
+3 -23
View File
@@ -1,31 +1,11 @@
<script> <script>
import Views from "./Views.svelte"; import Views from "./Views.svelte";
export let params; import {WorkingHoursViewsRepository} from "../components/Repositories.svelte";
export let params
params = { params = {
title: 'Jahresansicht', title: 'Jahresansicht',
records: [], promise: WorkingHoursViewsRepository.yearly()
isLoading: true
} }
const View = {
'browse': () => {
fetch(env.API_URL + '/views/working-hours/yearly')
.then(res => {
if (!res.ok) {
throw new Error('Fail!');
}
return res.json();
})
.then(data => {
params.records = data;
params.isLoading = false;
})
.catch(err => {
params.isLoading = false;
console.log(err);
});
}
};
View.browse();
</script> </script>
<Views {params} /> <Views {params} />