Commit 3a557a8f authored by Taddeüs Kroes's avatar Taddeüs Kroes

Implemented pages to add/edit matches

parent 85752895
CACHE_DIR := .cache
WWW_DIR := www
LIB_DIR := vendor
SCRIPTS := forms
SCRIPTS := forms scores
STYLES := main
HTTPDUSER := www-data
......
No preview for this file type
window.Nette.addError = (elem, message) ->
elem.focus() if elem.focus
if message
elem.setCustomValidity(message)
elem.valid = 0
elem.checkValidity()
#window.Nette.addError = (elem, message) ->
# elem.focus() if elem.focus
#
# if message
# elem.setCustomValidity(message)
# elem.valid = 0
# elem.checkValidity()
$ ->
$('input:first', document.forms[0]).focus() if document.forms.length
$('form:not(.nofocus) input:first', document.forms[0]).focus() if document.forms.length
create_addon = (input) ->
group = $('<div class="input-group">').insertBefore(input)
$(input).remove().appendTo(group)
$('<div class="input-group-addon">').appendTo(group)
$('input[type=range]').each ->
create_addon(@)
$(@).on 'change input', ->
$(@).next('.input-group-addon').text($(@).val())
$(@).change()
$('input[type^=date]').each ->
create_addon(@).html('<span class="glyphicon glyphicon-calendar"></span>')
$('input[type=number]').on('focus', -> $(@).select())
$('input[type=radio][data-inline]').each ->
group = $('<label class="radio-inline">')
.append($(@).parent('label').html())
$(@).closest('.radio').replaceWith(group)
$('tr').on 'click', ->
href = $(this).data('href')
......
num = (s) -> if s == '' or isNaN(parseInt(s)) then 0 else parseInt(s)
$ ->
$('.match').each ->
table = $(@)
match_total = table.find('.match-total')
match_avg = table.find('.match-avg')
inputs = table.find('input')
sum_row = (row) ->
row_total = 0
prev_total = 0
incomplete = false
row.find('input').each ->
if $(@).val() == ''
incomplete = true
false
row_total += num($(@).val())
if incomplete
return
if prev_row = row.prev('tr')
prev_total = num(prev_row.children('.total').text())
row.children('.row-total').text(row_total)
row.children('.total').text(prev_total + row_total)
row.children('.total').text(prev_total + row_total)
inputs.on 'change keyup', ->
sum = 0
count = 0
inputs.each ->
if $(@).val() != ''
sum += num($(@).val())
count++
sum_row($(@).closest('tr'))
match_total.text(sum)
match_avg.text(Math.round(sum / count * 10) / 10)
$('textarea').on 'keyup', ->
$(@).height(0).height($(@).prop('scrollHeight'))
desired_scroll = $(@).offset().top - 60
if $(document).scrollTop() < desired_scroll
$(window).scrollTop(desired_scroll)
.keyup()
......@@ -4,7 +4,7 @@ use Nette\Forms\Form;
use Instante\Bootstrap3Renderer\BootstrapRenderer;
$app->get('/', function () use ($app) {
$app->redirect(ROOT_URL . '/match');
$app->redirect(ROOT_URL . '/matches');
});
function login_form() {
......
......@@ -46,12 +46,21 @@ function render_match_action($action) {
};
}
function map_format($format, $array) {
$arr = array();
foreach ($array as $key => $value)
$arr[$key] = sprintf($format, $value);
return $arr;
}
function filter_form($matches) {
global $db;
$form = new Form;
$form->setRenderer(new BootstrapRenderer);
$form->setAction('match#results');
$form->setAction('matches#results');
$form->setMethod('get');
$match_ids = array_unique($matches->fetchPairs(null, 'id'));
......@@ -63,6 +72,7 @@ function filter_form($matches) {
$distances = $matches->fetchPairs('distance', 'distance');
asort($distances);
$distances = map_format('%d meters', $distances);
$form->addSelect('distance', 'Distance', $distances)
->setPrompt('Select a distance');
......@@ -74,9 +84,68 @@ function filter_form($matches) {
return $form;
}
$app->get('/match', function ($filter=null) use ($app, $db, $user) {
$user_id = $user->getId();
$matches = $db->table('match')->where(compact('user_id'))->order('created_at DESC');
function create_match_form() {
global $db, $user;
$disciplines = array('barebow', 'recurve', 'compound');
$disciplines = array_combine($disciplines, $disciplines);
$form = new Form;
$form->setRenderer(new BootstrapRenderer);
$form->setAction('match');
$form->addText('name', 'Name')
->setRequired();
$form->addText('created_at', 'Date')
->setType('datetime-local')
->setDefaultValue(strftime('%Y-%m-%dT%H:%M'))
->setAttribute('placeholder', 'YYYY-MM-DD HH:MM')
->addRule(Form::PATTERN, 'Invalid date', '\d{4}-\d{1,2}-\d{1,2}[T ]\d{1,2}:\d{1,2}')
->setRequired();
$form->addText('distance', 'Distance')
->setAttribute('placeholder', 'distance in meters')
->setType('number')
->setRequired();
$form->addRadioList('discipline', 'Discipline', $disciplines)
->setAttribute('data-inline', true)
->setRequired();
$form->addText('arrows', 'Arrows')
->setType('range')
->setAttribute('min', 1)
->setAttribute('max', 10)
->setDefaultValue(3)
->setRequired();
$form->addText('turns', 'Turns')
->setType('number')
->setAttribute('min', 1)
->setDefaultValue(10)
->setRequired();
$form->addTextarea('notes', 'Notes');
$form->addSubmit('send', 'Create');
// Set attributes from last match as default, since the shooter is likely
// to repeat the previous training in many cases
$last_match = $db->table('match')
->where(array('user_id' => $user->getId()))
->order('created_at DESC')
->fetch();
if ($last_match) {
//$form['name']->setDefaultValue($last_match->name);
$form['distance']->setDefaultValue($last_match->distance);
$form['discipline']->setDefaultValue($last_match->discipline);
$form['arrows']->setDefaultValue($last_match->arrows);
$form['turns']->setDefaultValue($last_match->turns);
}
return $form;
}
$app->get('/matches', function ($filter=null) use ($app, $db, $user) {
$matches = $db->table('match')
->where(array('user_id' => $user->getId()))
->order('created_at DESC');
$filter_form = filter_form($matches);
......@@ -102,10 +171,40 @@ $app->get('/match', function ($filter=null) use ($app, $db, $user) {
});
$app->get('/match/:id', render_match_action('view'));
$app->get('/match/:id/edit', render_match_action('edit'));
$app->get('/match/:id/scores', render_match_action('scores'));
$app->get('/match/:id/delete', render_match_action('delete'));
$app->get('/match', function () use ($app) {
$app->render('match/create', array('form' => create_match_form()));
});
$app->post('/match', function () use ($app, $db, $user) {
$form = create_match_form();
$form->validate();
if (!$form->hasErrors()) {
$values = $form->getValues();
$values->created_at = str_replace('T', ' ', $values->created_at) . ':00';
$values->scores = pack_scores(array_fill(0, $values->turns * $values->arrows, 0));
$values->user_id = $user->getId();
$match = $db->table('match')->insert($values);
$app->redirect(ROOT_URL . "/match/{$match->id}/scores");
}
$app->render('match/create', compact('form'));
});
$app->post('/match/:id/scores', function ($id) use ($app, $db) {
find_match($id)->update(array(
'scores' => pack_scores(array_map('intval', $_POST['scores'])),
'notes' => $_POST['notes']
));
$app->redirect(ROOT_URL . '/match/' . $id);
});
$app->delete('/match/:id', function ($id) {
find_match($id)->delete();
$app->redirect(ROOT_URL . '/matches');
});
$app->put('/match/:id', function ($id) {
......
......@@ -28,7 +28,13 @@ $xs-width: 768px
font-size: 15px
float: right
margin-top: 15px
display: block
.btn-group
float: right
margin-top: 4px
.well-form
padding-bottom: 3px
.match
margin-top: 5px
......@@ -42,11 +48,21 @@ $xs-width: 768px
.separator
border-right: 4px double
//@media (max-width: $xs-width - 1)
// .actions
// float: right
input
border: none
width: 100%
.panel textarea
border: none
width: 100%
min-width: 100%
max-width: 100%
min-height: 21px
overflow-y: hidden
white-space: normal
.actions
float: right
margin-bottom: 15px
.tags span
......
{var $menu = [
$user->isLoggedIn() ? ['match', 'Matches'],
$user->isLoggedIn() ? ['matches', 'Matches'],
$user->isLoggedIn() ? ['logout', 'Logout'] : ['login', 'Login'],
!$user->isLoggedIn() ? ['register', 'Register'],
]}
......@@ -10,7 +10,7 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<base href="{$config['root_url']}/">
<link rel="stylesheet" href="css/main.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
</head>
<body>
<nav class="navbar navbar-default navbar-static-top" role="navigation">
......@@ -40,7 +40,10 @@
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="js/netteForms.js"></script>
{*<script src="js/netteForms.js"></script>*}
<script src="js/forms.js"></script>
{ifset #scripts}
{include scripts}
{/ifset}
</body>
</html>
{extends '../layout.latte'}
{block content}
<h2 class="page-header">Add new match</h2>
{form $form}
{form errors}
{form controls}
<div class="form-group">
<div class="form-actions col-sm-offset-2 col-sm-10">
<div class="btn-group actions">
<a href="matches" class="btn btn-default" title="Cancel">
<span class="glyphicon glyphicon-remove"></span>
</a>
<button type="submit" name="send" class="btn btn-primary" title="Save">
<span class="glyphicon glyphicon-save"></span>
</button>
</div>
</div>
</div>
{/form}
{/block}
......@@ -5,7 +5,8 @@
Delete match
</h2>
<p>
Are you sure you want to delete the match "{$match->name}"? This action cannot be undone.
Are you sure you want to delete the match <em>"{$match->name}"</em>?
This action cannot be undone.
</p>
<form action="match/{$match->id}" method="post">
......
{extends '../layout.latte'}
{block content}
<h2 class="page-header">{$match->name}</h2>
{/block}
{extends '../layout.latte'}
{block content}
<h3 class="page-header">
<a data-toggle="collapse" href="#filter">
Filter matches <span class="caret"></span>
</a>
</h3>
<h2 class="page-header">
Matches
<div class="btn-group">
<a href="match" title="Add new match" class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-plus"></span>
</a>
<a data-toggle="collapse" href="#filter" title="Filter"
class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-filter"></span>
</a>
</div>
</h2>
<div id="filter" class="collapse {$filter_form->isSubmitted() ? in}">
{form $filter_form}
{form errors}
{form controls}
<div class="form-group">
<div class="form-actions col-sm-offset-2 col-sm-10">
<button type="submit" name="send" class="btn btn-primary">Filter</button>
<a href="match" class="btn btn-default">Clear filters</a>
<div class="nofocus well well-form">
{form $filter_form}
{form errors}
{form controls}
<div class="form-group">
<div class="form-actions col-sm-offset-2 col-sm-10">
<button type="submit" name="send" class="btn btn-primary">Filter</button>
<a href="matches" class="btn btn-default">Clear filters</a>
</div>
</div>
</div>
{/form}
<h3>Results</h3>
{/form}
</div>
</div>
<table id="results" class="table table-hover matches">
......
{extends '../layout.latte'}
{var $rows = array_chunk(unpack_scores($match->scores), $match->arrows)}
{var $total = 0}
{var $arrow_cols = array(3 => 4, 4 => 5, 5 => 5, 6 => 6, 7 => 6)}
{var $cols = isset($arrow_cols[$match->arrows]) ? $arrow_cols[$match->arrows] : 4}
{var $tags = $match->related('tag')}
{block content}
<h2 class="page-header">{$match->name}</h2>
<p>
{$match->distance}m {$match->discipline|capitalize} -
{$match->created_at|date:'%A %e %B %Y %H:%m'}
</p>
<div class="row">
<div class="col-sm-{$cols + 2} col-md-{$cols + 1} col-lg-{$cols}">
<form method="post" action="match/{$match->id}/scores">
<table class="table table-bordered table-condensed match">
<thead>
<tr>
<th class="separator"></th>
<th class="separator" colspan="{$match->arrows}">Points</th>
<th colspan="2">Subtotal</th>
</tr>
</thead>
<tbody>
<tr n:foreach="$rows as $i => $row">
<th class="separator">{$i + 1}</th>
<td n:foreach="$row as $j => $arrow"
n:class="$j == $match->arrows - 1 ? separator">
<input type="number" min="0" max="10" name="scores[]" value="{$arrow ? $arrow}">
</td>
<td class="row-total">{$sum = array_sum($row)}</td>
<td class="total">{$total = $total + $sum}</td>
</tr>
</tbody>
<tbody>
<tr>
<th colspan="{$match->arrows + 2}">Total:<br>Average:</th>
<td>
<span class="match-total">{$total}</span><br>
<span class="match-avg">{$total / ($match->turns * $match->arrows)|number:1}</span>
</td>
</tr>
</tbody>
</table>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Notes</h3>
</div>
<div class="panel-body">
<textarea name="notes" wrap="hard" placeholder="Click here to make a note"
>{$match->notes}</textarea>
</div>
</div>
<div class="actions">
<button type="submit" class="btn btn-primary" title="Save">
<span class="glyphicon glyphicon-ok"></span>
</button>
</div>
</form>
</div>
</div>
{/block}
{block scripts}
<script src="js/scores.js"></script>
{/block}
......@@ -7,17 +7,14 @@
{var $tags = $match->related('tag')}
{block content}
<h2 class="page-header">
{$match->name}
{*<a href="match" class="back">&laquo; Go back</a>*}
</h2>
<h2 class="page-header">{$match->name}</h2>
<p>
{$match->distance}m {$match->discipline|capitalize} -
{$match->created_at|date:'%A %e %B %Y %H:%m'}
</p>
<p n:if="$tags->count()" class="tags">
<strong>Tags:</strong>
<a n:foreach="$tags as $tag" href="tag/{$tag->name}">
<a n:foreach="$tags as $tag" href="match?tag={$tag->name}">
<span class="label label-primary">{$tag->name}</span>
</a>
</p>
......@@ -25,6 +22,14 @@
<div class="row">
<div class="col-sm-{$cols + 2} col-md-{$cols + 1} col-lg-{$cols}">
{*
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Scores</h3>
</div>
*}
<table class="table table-bordered table-condensed match">
<thead>
<tr>
......@@ -44,17 +49,26 @@
</tbody>
<tbody>
<tr>
<th colspan="{$match->arrows + 2}">Total:</th>
<td>{$total}</td>
<th colspan="{$match->arrows + 2}">Total:<br>Average:</th>
<td>{$total}<br>{$total / ($match->turns * $match->arrows)|number:1}</td>
</tr>
</tbody>
</table>
{*</div>*}
<div n:if="$match->notes" class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Notes</h3>
</div>
<div class="panel-body">{htmlentities($match->notes, ENT_QUOTES)|noescape|nl2br}</div>
</div>
<div class="btn-group actions">
<a href="match" class="btn btn-default" title="Go back">
<span class="glyphicon glyphicon-chevron-left"></span>
<a href="match/{$match->id}/edit" class="btn btn-default" title="Edit metadata">
<span class="glyphicon glyphicon-cog"></span>
</a>
<a href="match/{$match->id}/edit" class="btn btn-default" title="Edit">
<a href="match/{$match->id}/scores" class="btn btn-default" title="Edit scores">
<span class="glyphicon glyphicon-pencil"></span>
</a>
<a href="match/{$match->id}/delete" class="btn btn-danger" title="Delete">
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment