WIP: Vykuchano + zmena schematu.

This commit is contained in:
severak 2021-07-15 20:42:46 +02:00
parent eced140941
commit 128118ec36
13 changed files with 28 additions and 1148 deletions

523
app.php
View File

@ -16,7 +16,7 @@ $singletons['rows'] = function(){
// HP & LOGIN
route('', '/', function (){
if (!user()) return redirect('/login/');
// if (!user()) return redirect('/login/');
return render('home');
});
@ -89,528 +89,7 @@ route('', '/zmena-hesla/', function ($req){
return render('form', ['form'=>$form, 'title'=>'Změnit heslo']);
});
// SKLAD
route('GET', '/sklad/', function ($req){
if (!user()) return redirect('/login/');
/** @var severak\database\rows $rows */
$rows = di('rows');
$items = $rows->page('items', ['is_active'=>1], ['ord'=>'asc']);
return render('items', ['items'=>$items]);
});
$singletons['nabidka_form'] = function (){
$form = new severak\forms\form(['method'=>'POST']);
$form->field('name', ['required'=>true, 'label'=>'Název']);
$form->field('price', ['type'=>'number', 'label'=>'Cena']);
$form->field('note', ['type'=>'textarea', 'label'=>'Poznámka']);
$form->field('ord', ['type'=>'number', 'label'=>'Pořadí']);
$form->field('is_amount_tracked', ['type'=>'checkbox', 'label'=>'Hlídat počet na skladě?']);
$form->field('amount', ['type'=>'number', 'label'=>'Počet na skladě']);
$form->field('_save', ['type'=>'submit', 'label'=>'Přidat']);
$form->rule('price', function ($f){
return $f > 0 || $f < 0;
}, 'Cena nemůže být nulová.');
return $form;
};
route('', '/sklad/pridat/', function ($req){
if (!user()) return redirect('/login/');
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
/** @var severak\forms\form $form */
$form = di('nabidka_form');
if ($req->getMethod()=='POST') {
$form->fill($req->getParsedBody());
if ($form->validate()) {
$rows->insert('items', [
'name'=>$form->values['name'],
'price'=>$form->values['price'],
'note'=>$form->values['note'],
'ord'=>$form->values['ord'],
'amount'=>$form->values['amount'],
'is_amount_tracked'=>$form->values['is_amount_tracked'],
]);
return redirect('/sklad/');
}
}
return render('form', ['form'=>$form, 'title'=>'Přidat položku']);
});
route('', '/sklad/upravit/{id}/', function ($req, $params){
if (!user()) return redirect('/login/');
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
/** @var severak\forms\form $form */
$form = di('nabidka_form');
$item = $rows->one('items', $params['id']);
if (!$item) return notFound();
$form->fill($item);
if ($req->getMethod()=='POST') {
$form->fill($req->getParsedBody());
if ($form->validate()) {
$rows->update('items', [
'name'=>$form->values['name'],
'price'=>$form->values['price'],
'note'=>$form->values['note'],
'ord'=>$form->values['ord'],
'amount'=>$form->values['amount'],
'is_amount_tracked'=>$form->values['is_amount_tracked'],
], $params['id']);
return redirect('/sklad/');
}
}
return render('form', ['form'=>$form, 'title'=>'Upravit položku']);
});
// TODO - tohle nechceme přes GET
route('', '/sklad/smazat/{id}/', function ($req, $params){
if (!user()) return redirect('/login/');
/** @var severak\database\rows $rows */
$rows = di('rows');
$rows->update('items', ['is_active'=>0], ['id'=>$params['id'] ]);
return redirect('/sklad/');
});
// ČLENOVÉ
route('', '/clenove/', function ($req){
if (!user()) return redirect('/login/');
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
if ($_POST['qrcode']) {
$card = $rows->one('cards', ['id'=>$_POST['qrcode']]);
if ($card) {
return redirect('/clenove/detail/'. $card['member_id'] . '/');
} else {
flash('Karta není registrována.', 'error');
return redirect('/clenove/');
}
}
$searchFor = $_GET['searchFor'] ?? null;
$page = $_GET['page'] ?? 1;
if ($searchFor) {
$searchSql = '%' . $searchFor . '%';
$members = $rows->more('members', $rows->fragment('name LIKE ? OR email LIKE ? OR phone LIKE ?', [$searchSql, $searchSql, $searchSql]));
$pages = 1;
} else {
$members = $rows->page('members', [], ['name'=>'asc'], $page, 30);
$pages = $rows->pages;
}
return render('members', ['members'=>$members, 'page'=>$page, 'pages'=>$pages, 'searchFor'=>$searchFor]);
});
function items_sold(rows $rows, $od, $do) {
$tsOd = strtotime($od);
$tsDo = strtotime($do);
return $rows->execute($rows->query('SELECT item_id, SUM(amount) AS amount FROM sold_items WHERE date>? AND date<?', [$tsOd, $tsDo]))->fetchAll(PDO::FETCH_KEY_PAIR);
}
route('', '/sklad/prodano/', function ($req){
if (!user()) return redirect('/login/');
$user = user();
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$this_week = items_sold($rows, 'monday this week', 'now');
$last_week = items_sold($rows, 'monday last week', 'sunday last week +24 hours -1 sec');
$this_month = items_sold($rows, 'first day of this month midnight', 'last day of this month midnight +24 hours -1 sec');
$last_month = items_sold($rows, 'first day of last month midnight', 'last day of last month midnight +24 hours -1 sec');
$items = $rows->page('items', ['is_active'=>1, 'is_amount_tracked'=>1], ['ord'=>'asc']);
return render('items_sold', ['items'=>$items, 'this_week'=>$this_week, 'last_week'=>$last_week, 'this_month'=>$this_month, 'last_month'=>$last_month]);
});
route('', '/clenove/pridat/', function ($req){
if (!user()) return redirect('/login/');
$user = user();
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$form = new severak\forms\form(['method'=>'POST']);
$form->field('card_id', ['required'=>true, 'type'=>'number', 'label'=>'Číslo karty', 'id'=>'qrcode']);
$form->field('name', ['required'=>true, 'label'=>'Jméno']);
$form->field('email', ['type'=>'email', 'label'=>'E-mail']);
$form->field('phone', ['type'=>'phone', 'label'=>'Telefon']);
$form->field('date_of_birth', ['type'=>'date', 'label'=>'Datum narození']);
$form->field('_save', ['type'=>'submit', 'label'=>'Přidat']);
if ($req->getMethod()=='POST' && $form->fill($req->getParsedBody()) && $form->validate()) {
$card = $rows->one('cards', $form->values['card_id']);
if ($card) {
$form->error('card_id', 'Karta již je registrovaná v systému!');
}
// TODO - tyhle duplikáty řešit jinak
if ($rows->one('members', ['name'=>$form->values['name']])) {
$form->error('name', 'Tento člen již kartičku má!');
}
if (!empty($form->values['email']) && $rows->one('members', ['email'=>$form->values['email']])) {
$form->error('email', 'Tento email již má některý člen.');
}
if (!empty($form->values['phone']) && $rows->one('members', ['phone'=>$form->values['phone']])) {
$form->error('phone', 'Tento telefon již má některý člen.!');
}
if ($form->isValid) {
$memberId = $rows->insert('members', [
'name'=>$form->values['name'],
'email'=>$form->values['email'],
'phone'=>$form->values['phone'],
'date_of_birth'=>$form->values['date_of_birth'],
]);
$rows->insert('cards', [
'id'=>$form->values['card_id'],
'member_id'=>$memberId,
'issued_by'=>$user['id'],
'issued_at'=>time(),
'is_active'=>1
]);
flash('Člen byl úspěšně registrován.');
return redirect('/');
}
}
return render('form', ['form'=>$form, 'title'=>'Přidat člena']);
});
route('', '/clenove/detail/{id}/', function ($req, $params) {
if (!user()) return redirect('/login/');
$user = user();
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$member = $rows->one('members', $params['id']);
if (!$member) return notFound();
$page = $_GET['page'] ?? 1;
$transactions = $rows->page('transactions', ['member_id'=>$params['id']], ['issued_at'=>'desc'], $page, 30);
$cards = $rows->more('cards', ['member_id'=>$params['id']], ['issued_at'=>'desc']);
$pages = $rows->pages;
return render('member_detail', ['member'=>$member, 'page'=>$page, 'pages'=>$pages, 'transactions'=>$transactions, 'cards'=>$cards]);
});
route('', '/clenove/upravit/{id}/', function ($req, $params) {
if (!user()) return redirect('/login/');
$user = user();
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$member = $rows->one('members', $params['id']);
if (!$member) return notFound();
$form = new severak\forms\form(['method'=>'POST']);
$form->field('name', ['required'=>true, 'label'=>'Jméno']);
$form->field('email', ['type'=>'email', 'label'=>'E-mail']);
$form->field('phone', ['type'=>'phone', 'label'=>'Telefon']);
$form->field('date_of_birth', ['type'=>'date', 'label'=>'Datum narození']);
$form->field('note', ['type'=>'textarea', 'rows'=>3, 'label'=>'Poznámka']);
$form->field('is_active', ['type'=>'checkbox', 'label'=>'Je aktivní?']);
$form->field('_save', ['type'=>'submit', 'label'=>'Upravit']);
$form->fill($member);
if ($req->getMethod()=='POST') {
$form->fill($req->getParsedBody());
// TODO - zde nějak ošetřovat duplicity
if ($form->validate()) {
$rows->update('members', [
'name'=>$form->values['name'],
'email'=>$form->values['email'],
'phone'=>$form->values['phone'],
'date_of_birth'=>$form->values['date_of_birth'],
'note'=>$form->values['note'],
'is_active'=>$form->values['is_active'] ?? 0,
], $params['id']);
if (!$form->values['is_active']) {
// deaktivujeme kartičku
$rows->update('cards', ['is_active'=>0, 'note'=>'deaktivována s uživatelem'], ['is_active'=>'1', 'member_id'=>$params['id']]);
}
if (!$member['is_active'] && $form->values['is_active']) {
flash('Nyní musíte vystavit novou kartičku.', 'warning');
}
return redirect('/clenove/detail/'. $params['id'].'/');
}
}
return render('form', ['form'=>$form, 'title'=>'Upravit člena']);
});
route('', '/clenove/nova_karta/{id}/', function ($req, $params) {
if (!user()) return redirect('/login/');
$user = user();
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$member = $rows->one('members', $params['id']);
if (!$member) return notFound();
$reasons = [
'ztracená' => 'karta byla ztracená',
'ukradená' => 'karta byla ukradená',
'obnovení členství' => 'obnovení členství'
];
$form = new severak\forms\form(['method'=>'POST']);
$form->field('card_id', ['required'=>true, 'type'=>'number', 'label'=>'Číslo karty', 'id'=>'qrcode']);
$form->field('reason', ['type'=>'select', 'label'=>'Důvod vydání nové karty', 'options'=>$reasons]);
$form->field('block_original', ['type'=>'checkbox', 'label'=>'zablokovat původní kartu']);
$form->field('_save', ['type'=>'submit', 'label'=>'Vystavit novou kartu']);
if ($req->getMethod()=='POST' && $form->fill($req->getParsedBody()) && $form->validate()) {
$card = $rows->one('cards', $form->values['card_id']);
if ($card) {
$form->error('card_id', 'Karta již je registrovaná v systému!');
}
$form->fill($req->getParsedBody());
if ($form->validate()) {
// deaktivujeme původní kartu
$rows->update('cards', [
'is_active' => 0,
'is_blocked' => $form->values['block_original'] ?? 0,
'note' => $form->values['reason']
], ['is_active' => '1', 'member_id' => $params['id']]);
// přidáváme novou
$rows->insert('cards', [
'id'=>$form->values['card_id'],
'member_id'=>$params['id'],
'issued_by'=>$user['id'],
'issued_at'=>time(),
'is_active'=>1
]);
return redirect('/clenove/detail/'. $params['id'].'/');
}
}
return render('form', ['form'=>$form, 'title'=>'Nová karta']);
});
// POKLADNA:
route('','/pokladna/', function(){
return render('pokladna', ['title'=>'pokladna']);
});
route('', '/pokladna/dobit/', function ($req){
if (!user()) return redirect('/login/');
$user = user();
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$form = new form(['method'=>'post']);
$form->field('card_id', ['required'=>true, 'type'=>'number', 'label'=>'Číslo karty', 'id'=>'qrcode']);
$form->field('amount', ['required'=>true, 'type'=>'number', 'label'=>'Částka']);
$form->field('_sbt', ['label'=>'Vložit', 'type'=>'submit']);
// TODO - zde kontrolovat maxmální a minimální výši nabití
if ($req->getMethod()=='POST' && $form->fill($req->getParsedBody()) && $form->validate()) {
$card = $rows->one('cards', ['id'=>$form->values['card_id']]);
if (!$card || !$card['is_active']) {
$form->error('card_id', 'Neznámá/neplatná karta!');
}
if ($card && $card['is_blocked']) {
$form->error('card_id', 'Karta je zablokovaná.');
}
if ($card) {
$member = $rows->one('members', $card['member_id']);
}
if ($form->isValid) {
// BIG TODO - tohle dělat v databázové transakci
$rows->insert('transactions', [
'member_id' => $member['id'],
'card_id' => $card['id'],
'issued_by'=>$user['id'],
'issued_at'=>time(),
'amount'=>$form->values['amount'],
'is_cash'=>1
]);
$rows->execute($rows->query('UPDATE members SET balance = balance + ? WHERE id=?', [$form->values['amount'], $member['id']]));
flash('Kredit úspěšně dobit!', 'success');
return redirect('/');
}
}
return render('form', ['form'=>$form, 'title'=>'Dobít kartu']);
});
// TODO - zůstatek, vybrat
// BAR:
route('GET', '/bar/', function ($req){
if (!user()) return redirect('/login/');
$user = user();
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$nabidka = $rows->more('items', ['is_active'=>1], ['ord'=>'asc']);
return render('bar', ['items'=>$nabidka]);
});
route('POST', '/bar/userinfo/', function ($req){
if (!user()) return jsonResponse(['error'=>'Unauthorized.'], 403);
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$Q = $req->getParsedBody();
if (empty($Q['card_id'])) {
return jsonResponse(['error'=>'Špatný formát čísla karty.']);
}
$card = $rows->one('cards', $Q['card_id']);
if ($card && $card['is_blocked']) {
return jsonResponse(['error'=>'Karta je zablokovaná.']);
}
if (!$card || !$card['is_active']) {
return jsonResponse(['error'=>'Karta není aktivní.']);
}
$member = $rows->one('members', $card['member_id']);
if ($member['balance']==0) {
return jsonResponse(['error'=>'Karta není nabitá.']);
}
$dobMember = date_create($member['date_of_birth']);
$before18Years = date_create('now - 18 years');
$canBuyAlcohol = $dobMember && ($dobMember < $before18Years);
return jsonResponse([
'name' => $member['name'],
'balance' => $member['balance'],
'can_buy_alcohol' => $canBuyAlcohol,
]);
});
route('POST', '/bar/buy/', function ($req){
if (!user()) return jsonResponse(['error'=>'Vypršelo přihlášení.']);
$user = user();
/** @var Psr\Http\Message\ServerRequestInterface $req */
/** @var severak\database\rows $rows */
$rows = di('rows');
$Q = $req->getParsedBody();
if (empty($Q['card_id'])) {
return jsonResponse(['error'=>'Špatný formát čísla karty.']);
}
$card = $rows->one('cards', $Q['card_id']);
if ($card && $card['is_blocked']) {
return jsonResponse(['error'=>'Karta je zablokovaná.']);
}
if (!$card || !$card['is_active']) {
return jsonResponse(['error'=>'Karta není aktivní.']);
}
$member = $rows->one('members', $card['member_id']);
if ($member['balance']<1) {
return jsonResponse(['error'=>'Karta není nabitá.']);
}
$totalSum = 0;
foreach ($Q['items'] as $item) {
$totalSum = $totalSum + ($item['price'] * $item['amount']);
}
if ($member['balance']<$totalSum) {
return jsonResponse(['error'=>'Na kartě není dostatek peněz.', 'balance'=>$member['balance']]);
}
$transcactionId = $rows->insert('transactions', [
'member_id' => $member['id'],
'card_id' => $card['id'],
'issued_by'=>$user['id'],
'issued_at'=>time(),
'amount'=>$totalSum * -1,
'items' => json_encode($Q['items']),
'is_cash'=>0
]);
$rows->execute($rows->query('UPDATE members SET balance = balance - ? WHERE id=?', [$totalSum, $member['id']]));
$isAmountTracked = array_column($rows->more('items'), 'is_amount_tracked', 'id');
foreach ($Q['items'] as $item) {
if ($item['id'] && $isAmountTracked[$item['id']]) {
$rows->insert('sold_items', [
'item_id' => $item['id'],
'transaction_id' => $transcactionId,
'amount' => $item['amount'],
'date'=>time()
]);
$rows->execute($rows->query('UPDATE items SET amount=amount-1 WHERE id=?', [$item['id']]));
}
}
return jsonResponse(['success'=>true]);
});
// OBSLUHA

View File

@ -1,6 +1,6 @@
<?php
// path to database
$config['database'] = 'db/stela.sqlite';
$config['database'] = 'db/pruvodce.sqlite';
// MD5 of adminer password
$config['adminer_password'] = md5('heslo');
// secret key (throw something from random.org here)

View File

@ -1,4 +1,22 @@
-- Adminer 4.7.5 SQLite 3 dump
CREATE TABLE "poi" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"slug" text NOT NULL,
"name" text NOT NULL,
"description" text NOT NULL,
"cheatsheet" text NOT NULL,
"internal" text NOT NULL,
"is_public" integer NOT NULL DEFAULT '0'
);
CREATE UNIQUE INDEX "poi_slug" ON "poi" ("slug");
CREATE TABLE sqlite_sequence(name,seq);
INSERT INTO "sqlite_sequence" ("name", "seq") VALUES ('users', 1);
CREATE TABLE "users" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
@ -13,5 +31,6 @@ CREATE TABLE "users" (
CREATE UNIQUE INDEX "users_usename" ON "users" ("username");
INSERT INTO "users" ("id", "username", "password", "name", "note", "is_active", "is_superuser") VALUES (1, 'severak', '$2y$10$G//hwvWHJYNHFk6JNr3GG.kuzM/dI9UTtU2bxr9EvTTEg0YOc8mj.', 'Severák', '(testovací účet)', 1,1);
INSERT INTO "users" ("id", "member_id", "username", "password", "name", "note", "is_active", "is_superuser") VALUES (1, NULL, 'severak', '$2y$10$G//hwvWHJYNHFk6JNr3GG.kuzM/dI9UTtU2bxr9EvTTEg0YOc8mj.', 'Severák', '(testovací účet)', 1, 1);
--

View File

@ -1,136 +1,5 @@
</main>
</div>
</section>
<section class="section" id="qrscanner" style="display: none">
<div class="container" style="max-width: 800px">
<div id="qr_loadingMessage">🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>
<div style="max-width: 100%">
<canvas id="qr_canvas" hidden style="max-width: 100%"></canvas>
</div>
<div id="qr_output" hidden>
<div id="qr_outputMessage">No QR code detected.</div>
<div hidden><b>Data:</b> <span id="qr_outputData"></span></div>
</div>
<a href="#" id="qr_manual" class="button is-fullwidth">zadat QR kód ručně</a>
</div>
</section>
<script src="/static/uboot.js"></script>
<script>
whenReady(function () {
on('navbar-burger', 'click', function () {
if (hasClass('navbar-burger', 'is-active')) {
delClass('navbar-burger', 'is-active');
delClass('navbar-menu', 'is-active');
} else {
addClass('navbar-burger', 'is-active');
addClass('navbar-menu', 'is-active');
}
});
});
</script>
<script src="/static/jsQR.js"></script>
<script>
whenReady(function () {
var video = document.createElement("video");
var canvasElement = document.getElementById("qr_canvas");
var canvas = canvasElement.getContext("2d");
var loadingMessage = document.getElementById("qr_loadingMessage");
var outputContainer = document.getElementById("qr_output");
var outputMessage = document.getElementById("qr_outputMessage");
var outputData = document.getElementById("qr_outputData");
var localstream;
function videoOff() {
//clearInterval(theDrawLoop);
//ExtensionData.vidStatus = 'off';
video.pause();
video.src = "";
if (localstream) localstream.getTracks()[0].stop();
//console.log("Vid off");
}
function drawLine(begin, end, color) {
canvas.beginPath();
canvas.moveTo(begin.x, begin.y);
canvas.lineTo(end.x, end.y);
canvas.lineWidth = 4;
canvas.strokeStyle = color;
canvas.stroke();
}
function tick() {
loadingMessage.innerText = "⌛ Loading video..."
if (video.readyState === video.HAVE_ENOUGH_DATA) {
loadingMessage.hidden = true;
canvasElement.hidden = false;
outputContainer.hidden = false;
canvasElement.height = video.videoHeight;
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
var code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert",
});
if (code) {
drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
outputMessage.hidden = true;
outputData.parentElement.hidden = false;
outputData.innerText = code.data;
gebi('qrcode').value = code.data;
gebi('app').style.display = 'block';
gebi('qrscanner').style.display = 'none';
videoOff();
return;
} else {
outputMessage.hidden = false;
outputData.parentElement.hidden = true;
}
}
requestAnimationFrame(tick);
}
if (gebi('qrcode')) {
on('qrcode', 'click', function (event) {
// schovame app a odkryjem QR scanner
gebi('app').style.display = 'none';
gebi('qrscanner').style.display = 'block';
// Use facingMode: environment to attemt to get the front camera on phones
navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
video.srcObject = stream;
localstream = stream;
video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
video.play();
requestAnimationFrame(tick);
}).catch(function () {
gebi('app').style.display = 'block';
gebi('qrscanner').style.display = 'none';
gebi('qrcode').focus();
videoOff();
});
event.preventDefault();
});
on('qr_manual', 'click', function (event) {
gebi('app').style.display = 'block';
gebi('qrscanner').style.display = 'none';
gebi('qrcode').focus();
videoOff();
event.preventDefault();
});
}
});
</script>
</body>
</html>
</html>

View File

@ -3,10 +3,9 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php if (isset($title)) echo $title . ' - ' ; ?>Stela</title>
<title><?php if (isset($title)) echo $title . ' - ' ; ?>Průvodce</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<script src="/static/vue.min.js"></script>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
@ -17,7 +16,7 @@
<div class="container" style="max-width: 800px">
<div class="navbar-brand">
<a class="navbar-item" href="/">
&nbsp;<img src="/favicon-32x32.png">
Průvodce&nbsp;<img src="/favicon-32x32.png">
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample" id="navbar-burger">
@ -30,21 +29,6 @@
<div id="navbar-menu" class="navbar-menu">
<div class="navbar-start">
<?php if (user()) { ?>
<a class="navbar-item" href="/bar/">
bar
</a>
<a class="navbar-item" href="/pokladna/">
pokladna
</a>
<a class="navbar-item" href="/sklad/">
sklad
</a>
<a class="navbar-item" href="/clenove/">
členové
</a>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
@ -91,4 +75,4 @@
}
}
?>
<main>
<main>

View File

@ -1,239 +0,0 @@
<?=render('_header', ['title'=>$title ?? null]); ?>
<div id="barapp" >
<!-- nejdříve se přihlásíme přes https://cozmo.github.io/jsQR/ -->
<div class="message is-success" v-show="success && !name">
<div class="message-body">
Nákup proběhl úspěšně. Můžete zadat další.
</div>
</div>
<div class="field has-addons" v-show="!name">
<div class="control is-expanded">
<input name="searchFor" class="input" placeholder="číslo karty" v-model="card_id" @change="findUser" id="qrcode">
</div>
<div class="control">
<input type="submit" class="button is-info" value="přihlásit" @click="findUser">
</div>
</div>
<div class="message is-danger" v-show="!name && error">
<div class="message-body">
{{ error }}
</div>
</div>
<div v-show="name">
<div class="message" >
<div class="message-body">
<div class="columns is-mobile">
<div class="column"><span class="fas fa-user"></span>
{{ name }}</div>
<div class="column has-text-right">{{ balance }},-</div>
</div>
</div>
</div>
<div class="message is-danger" v-show="!can_buy_alcohol">
<div class="message-body">
Pod 18 let - neprodávat alkohol!
</div>
</div>
<table class="table" v-show="items.length">
<tr>
<th>název</th>
<th>/kus</th>
<th>počet</th>
<th>součet</th>
<th></th>
</tr>
<tr v-for="item in items" class="is-vcentered">
<td><input name="item[]" class="input is-static" placeholder="název zboží" v-model="item.name"></td>
<td><input name="price[]" inputmode="numeric" class="input is-static" v-model="item.price"></td>
<td><input name="amount[]" type="number" min="1" max="100" class="input is-static" v-model="item.amount"></td>
<td class="has-text-right"><div class="input is-static">{{ item.price * item.amount }}</div></td>
<td><div class="input is-static"><a class="delete" @click="removeItem(item)"></a></div></td>
</tr>
<tr>
<th colspan="3">celkem</th>
<th v-bind:class="totalClass" class="has-text-right">{{ totalAmount }}</th>
</tr>
</table>
<div class="message is-danger" v-show="error">
<div class="message-body">
{{ error }}
</div>
</div>
<div class="columns is-mobile">
<div class="column">
<button class="button is-light" @click="addItem">Přidat položku</button>
</div>
<div class="column has-text-right">
<input type="submit" value="Uložit" class="button is-primary" v-show="items.length" @click="completePurchase">
</div>
</div>
<div class="buttons">
<?php foreach ($items as $item) {
$warning = $item['price']<0 ? 'has-background-warning' : '';
?>
<button class="button is-fullwidth <?=$warning; ?>" @click="addPredefined" data-id="<?=$item['id']; ?>" data-name="<?=$item['name']; ?>" data-price="<?=$item['price']; ?>"><?=$item['name']; ?></button>
<?php } ?>
</div>
</div>
</div>
<script>
function sendData(url, data, onsuccess, onerror) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
onsuccess(response);
} else {
onerror();
}
};
xhr.send(JSON.stringify(data));
}
var app = new Vue({
el: '#barapp',
data: {
card_id: null,
name: null,
balance: -1,
can_buy_alcohol: false,
items : [],
error: false,
success: false
},
computed: {
totalAmount: function()
{
var total = 0;
this.items.forEach(function (item) {
total += item.price * item.amount;
});
return total;
},
totalClass: function () {
return {
'has-text-danger': this.totalAmount > this.balance
}
}
},
methods: {
reset: function() {
this.card_id= null;
this.name= null;
this.balance= -1;
this.can_buy_alcohol= false;
this.items = [];
this.error= false;
},
findUser: function(ev) {
var that = this;
// TODO - z nějakého důvodu se nepropisů QR kódy ze čtečky
that.card_id = gebi('qrcode').value;
sendData('/bar/userinfo/', {card_id: that.card_id}, function (response) {
if (response.error) {
that.error = response.error;
} else if (response.name) {
that.name = response.name;
that.balance = response.balance;
that.can_buy_alcohol = response.can_buy_alcohol;
that.items = [];
that.error = false;
}
}, function () {
that.error = 'Došlo k nečekané chybě.';
});
},
addPredefined: function(ev) {
var name = ev.target.getAttribute('data-name');
var price = ev.target.getAttribute('data-price');
var id = ev.target.getAttribute('data-id');
var alreadyThere = false;
this.items.forEach(function (item) {
if (item.id && item.id==id) {
item.amount++;
alreadyThere = true;
}
});
if (alreadyThere) return;
this.items.push({
name: name,
price: price,
amount: 1,
total: price,
id: id
});
},
addItem: function(){
this.items.push({
name: "",
price: 0,
amount: 1,
total: 0
});
},
changedItem: function(item) {
item.total = item.price * item.amount;
},
removeItem: function (item) {
if (item.amount>1) {
item.amount -= 1;
return;
}
this.items.splice(this.items.indexOf(item), 1)
},
completePurchase: function () {
var that = this;
sendData('/bar/buy/', {card_id: that.card_id, items: that.items}, function (response) {
if (response.error) {
that.error = response.error;
if (response.balance) {
that.balance = response.balance;
}
} else if (response.success) {
that.success = true;
that.reset();
}
}, function () {
that.error = 'Došlo k nečekané chybě.';
});
}
}
});
window.app = app;
</script>
<?=render('_footer');?>

View File

@ -1,9 +1,5 @@
<?= render('_header'); ?>
<div class="buttons">
<a href="/bar/" class="button is-fullwidth is-primary">zaplatit na baru</a>
<a href="/pokladna/dobit/" class="button is-fullwidth">dobít kredit</a>
<a href="/clenove/pridat/" class="button is-fullwidth">přidat člena</a>
</div>
<p>TODO</p>
<?= render('_footer'); ?>
<?= render('_footer'); ?>

View File

@ -1,38 +0,0 @@
<?=render('_header', ['title'=>'Sklad / stálé položky']);?>
<h1>Sklad <small>/ stálé položky</small></h1>
<?php if (count($items)) { ?>
<table class="table">
<tr><th>název</th><th>cena</th><th>počet</th><th></th><th></th></tr>
<?php foreach ($items as $item) { ?>
<tr>
<td><?=$item['name']; ?><br><small><?=$item['note']; ?></small></td>
<td><?=$item['price']; ?>,-</td>
<td class="has-text-right">
<?php
$class = 'is-success is-light';
if ($item['amount']<5) $class = 'is-danger';
if ($item['amount']>4 && $item['amount']<10) $class = 'is-warning';
if ($item['is_amount_tracked']) {
?>
<span class="tag <?=$class; ?>"><?=$item['amount']; ?></span>
<?php } else { ?>
---
<?php } ?>
</td>
<td><a href="/sklad/upravit/<?=$item['id']; ?>/" class=""><span class="icon"><i class="fas fa-edit"></i></span></a> </td>
<td><a href="/sklad/smazat/<?=$item['id']; ?>/" class="delete" onclick="return confirm('Opravdu chcete položku smazat?')">smazat</a> </td>
</tr>
<?php } ?>
</table>
<?php } else {
echo '<p>(nenalezeny žádné položky)</p><br>';
} ?>
<a href="/sklad/pridat/" class="button is-primary">Přidat položku</a>
<br><br>
<a href="/sklad/prodano/" class="button">Prodané položky</a>
<?=render('_footer');?>

View File

@ -1,21 +0,0 @@
<?= render('_header', ['title'=>'Prodané položky']); ?>
<h1>Prodané položky</h1>
<table class="table">
<thead>
<tr><th>název</th><th>tento týden</th><th>minulý týden</th><th>tento měsíc</th><th>minulý měsíc</th></tr>
</thead>
<tbody>
<?php foreach ($items as $item) { $item_id =$item['id']; ?>
<tr><td><?=$item['name']; ?></td><td><?=$this_week[$item_id] ?? ''; ?></td><td><?=$last_week[$item_id] ?? ''; ?></td><td><?=$this_month[$item_id] ?? ''; ?></td><td><?=$last_month[$item_id] ?? ''; ?></td></tr>
<?php } ?>
<?php if (empty($items)) { ?>
<tr><td colspan="5"><em>žádné </em></td></tr>
<?php } ?>
</tbody>
</table>
<?= render('_footer'); ?>

View File

@ -1,83 +0,0 @@
<?=render('_header', ['title'=>'detail člena']);?>
<div class="message" >
<div class="message-header">Detail člena</div>
<div class="message-body">
<div class="columns is-mobile">
<div class="column">
<?php if (!$member['is_active']) { ?><span class="tag is-warning">neaktivní</span> <?php } ?>
<span class="icon"><span class="icon fas fa-user"></span></span><?=$member['name']; ?></div>
<div class="column has-text-right"><?=$member['balance']; ?>,-</div>
</div>
<div class="columns">
<div class="column">
<?php if ($member['phone']) { ?>
<span class="icon"><span class="icon fas fa-phone"></span></span><?=$member['phone']; ?>
<?php } ?>
</div>
<div class="column has-text-right">
<?php if ($member['email']) { ?>
<span class="icon"><span class="fas fa-envelope"></span></span><?=$member['email']; ?>
<?php } ?>
</div>
</div>
<div class="content">
<u>karty:</u>
<ul><?php
foreach ($cards as $card) {
echo '<li>';
if ($card['is_active']) echo '<strong>';
echo $card['id']. ' <em>' . $card['note'] . '</em>';
if ($card['is_active']) echo '</strong>';
echo '</li>';
} ?>
</ul>
<p><?=$member['note']; ?></p>
</div>
<a href="/clenove/upravit/<?=$member['id']; ?>/" class="button">upravit</a>
<a href="/clenove/nova_karta/<?=$member['id']; ?>/" class="button">vystavit novou kartu</a>
</div>
</div>
<?php if (count($transactions)) { ?>
<h2 class="subtitle">poslední provedené transakce</h2>
<table class="table">
<tr><th>čas</th><th>částka</th><th></th><th></th></tr>
<?php foreach ($transactions as $tsx) {
$datum = date_create('@'.$tsx['issued_at']);
?>
<tr>
<td><?=$datum ? $datum->format('j.n.Y H:i') : ''; ?></td>
<td><?=$tsx['amount']; ?>,-</td>
<td>
<?php if ($tsx['is_cash']) {
echo 'dobytí';
} else if (json_decode($tsx['items'])) {
echo '<details><summary>nákup</summary><ul>';
$items = json_decode($tsx['items'], true);
foreach ($items as $item) {
echo '<li>'.$item['name'] . '';
if ($item['amount']>1) echo '(' . $item['amount'] . '*)';
echo ' ' . $item['total'] . ',-</li>';
}
echo '</ul></details>';
} ?>
</td>
</tr>
<?php } ?>
</table>
<?=render('_pagination', ['page'=>$page, 'pages'=>$pages]); ?>
<?php } else {
echo '<p>(nenalezeny žádné položky)</p><br>';
} ?>
<?=render('_footer');?>

View File

@ -1,52 +0,0 @@
<?=render('_header', ['title'=>'Členové']);?>
<form method="get">
<div class="field has-addons">
<div class="control is-expanded">
<input name="searchFor" class="input" type="search" placeholder="část jména, emailové adresy nebo telefonu" value="<?=$searchFor; ?>">
</div>
<div class="control">
<input type="submit" class="button is-info" value="Najít">
</div>
</div>
</form>
<br>
<h1>Členové</small></h1>
<?php if (count($members)) { ?>
<table class="table">
<tr><th>jméno</th><th>pozn.</th><th></th><th></th></tr>
<?php foreach ($members as $member) { ?>
<tr>
<td><?=$member['name']; ?><?php if (!$member['is_active']) { ?> <span class="tag is-warning">neaktivní</span><?php } ?></td>
<td><?=$member['note']; ?></td>
<td><a href="/clenove/detail/<?=$member['id']; ?>/" class=""><span class="icon"><i class="fas fa-search"></i></span></a> </td>
<td><a href="/clenove/upravit/<?=$member['id']; ?>/" class=""><span class="icon"><i class="fas fa-edit"></i></span></a> </td>
</tr>
<?php } ?>
</table>
<?=render('_pagination', ['page'=>$page, 'pages'=>$pages]); ?>
<?php } else {
echo '<p>(nenalezeny žádné položky)</p><br>';
} ?>
<a href="/clenove/pridat/" class="button is-primary">Přidat člena</a>
<br><br>
<form method="post">
<div class="field has-addons">
<div class="control is-expanded">
<input name="qrcode" class="input" type="search" placeholder="QR kód kartičky" id="qrcode">
</div>
<div class="control">
<input type="submit" class="button is-info" value="QR!">
</div>
</div>
</form>
<?=render('_footer');?>

View File

@ -1,5 +0,0 @@
<?= render('_header'); ?>
<a href="/pokladna/dobit/" class="button is-big is-primary">dobít kredit</a>
<?= render('_footer'); ?>

View File

@ -1,29 +0,0 @@
<?=render('_header', ['title'=>'Sklad']);?>
<h1>Sklad</small></h1>
<?php if (count($items)) { ?>
<table class="table">
<tr><th>název</th><th>cena</th><th>pozn.</th><th>počet na skladě</th><th></th></tr>
<?php foreach ($items as $item) { ?>
<tr>
<td><?=$item['name']; ?></td>
<td><?=$item['price']; ?>,-</td>
<td><?=$item['note']; ?></td>
<td class="has-text-right">
<?php
$class = '';
if ($item['amount']>0 && $item['amount']<5) $class = 'has-background-danger';
if ($item['amount']>4 && $item['amount']<10) $class = 'has-background-warning';
?>
<span class="<?=$class; ?>"><?=$item['amount']; ?></span>
</td>
<td><a href="/sklad/pocet/<?=$item['id']; ?>/" class=""><span class="icon"><i class="fas fa-edit"></i></span></a> </td>
</tr>
<?php } ?>
</table>
<?php } else {
echo '<p>(nenalezeny žádné položky)</p><br>';
} ?>
<?=render('_footer');?>