Files
crmPRO/templates/users/vacations.php
Jacek Pyziak a4a35c8d62 feat: Implement module permissions system with database-driven access control
- Added `users_permissions` table for managing user permissions.
- Created `PermissionRepository` for handling permission logic.
- Refactored `controls\Users::permissions()` to utilize the new database structure.
- Introduced AJAX endpoint for saving user permissions.
- Enhanced user management UI with permission checkboxes.
- Added vacation management template for handling employee absences.
- Implemented tests for `PermissionRepository`.
2026-02-26 20:17:03 +01:00

380 lines
15 KiB
PHP

<div class="form_container full vacations-page">
<div class="block-header">
<h2>Urlopy i <strong>nieobecno&#347;ci</strong></h2>
</div>
<div class="action_menu">
<? if ( $this -> can_switch_back ):?>
<a href="/users/back_to_admin/" class="btn btn-warning" title="Powr&#243;t do konta administratora">
<i class="fa fa-undo"></i> Powrot do admina
</a>
<? endif;?>
</div>
<div class="content">
<!-- Filtry -->
<form method="GET" action="/users/vacations/" class="filters-bar">
<select name="user_id">
<option value="0">Wszyscy pracownicy</option>
<? if ( is_array( $this -> users ) ): foreach ( $this -> users as $u ):?>
<option value="<?= (int) $u['id'];?>" <?= (int) $this -> filter_user_id === (int) $u['id'] ? 'selected' : '';?>>
<?= htmlspecialchars( $u['name'] . ' ' . $u['surname'] );?>
</option>
<? endforeach; endif;?>
</select>
<select name="year">
<? for ( $y = (int) date( 'Y' ) + 1; $y >= (int) date( 'Y' ) - 3; $y-- ):?>
<option value="<?= $y;?>" <?= (int) $this -> year === $y ? 'selected' : '';?>><?= $y;?></option>
<? endfor;?>
</select>
<button type="submit" class="btn btn-primary"><i class="fa fa-filter"></i> Filtruj</button>
<button type="button" class="btn btn-success" id="vacation-add-btn"><i class="fa fa-plus"></i> Dodaj urlop</button>
</form>
<!-- Zaległy urlop z poprzednich lat -->
<? if ( is_array( $this -> carryover ) and count( $this -> carryover ) ):?>
<div style="background: #fef3e0; border-left: 4px solid #e67e22; border-radius: 4px; padding: 14px 18px; margin-bottom: 20px;">
<div style="font-weight: 600; color: #e67e22; margin-bottom: 8px;">
<i class="fa fa-exclamation-triangle"></i> Zaleg&#322;y urlop z poprzednich lat
</div>
<? foreach ( $this -> carryover as $c ):?>
<div style="margin-bottom: 4px; color: #5a4e3a;">
<strong><?= htmlspecialchars( $c['name'] );?></strong>
&mdash; <strong><?= (int) $c['total'];?> dni</strong>
<span style="color: #999; margin-left: 4px;">(<?
$parts = [];
foreach ( $c['years'] as $yd )
$parts[] = (int) $yd['year'] . ': ' . (int) $yd['remaining'] . ' dni';
echo implode( ', ', $parts );
?>)</span>
</div>
<? endforeach;?>
</div>
<? endif;?>
<!-- Podsumowanie roczne -->
<div class="section-title">Podsumowanie roku <?= (int) $this -> year;?></div>
<table class="table">
<thead>
<tr>
<th>Pracownik</th>
<th style="width: 100px;" class="center">Limit dni</th>
<th style="width: 120px;" class="center">Wykorzystano</th>
<th style="width: 100px;" class="center">Pozosta&#322;o</th>
<th style="width: 120px;" class="center">Akcje</th>
</tr>
</thead>
<tbody>
<? if ( is_array( $this -> summary ) ): foreach ( $this -> summary as $s ):?>
<tr>
<td class="left"><?= htmlspecialchars( $s['name'] );?></td>
<td class="center"><?= (int) $s['limit'];?></td>
<td class="center"><?= (int) $s['used'];?></td>
<td class="center">
<strong style="color: <?= $s['remaining'] < 0 ? '#cc563d' : '#099885';?>;"><?= (int) $s['remaining'];?></strong>
</td>
<td class="center">
<button class="btn btn-success btn_small vacation-change-limit" data-user-id="<?= (int) $s['user_id'];?>" data-current-limit="<?= (int) $s['limit'];?>">
<i class="fa fa-pencil"></i> Zmie&#324; limit
</button>
</td>
</tr>
<? endforeach; endif;?>
</tbody>
</table>
<!-- Lista nieobecności -->
<div class="section-title">Nieobecno&#347;ci</div>
<table class="table">
<thead>
<tr>
<th>Pracownik</th>
<th style="width: 110px;">Od</th>
<th style="width: 110px;">Do</th>
<th style="width: 160px;">Typ</th>
<th style="width: 80px;" class="center">Dni rob.</th>
<th>Komentarz</th>
<th style="width: 80px;" class="center">Akcje</th>
</tr>
</thead>
<tbody>
<? if ( is_array( $this -> vacations ) and count( $this -> vacations ) ): foreach ( $this -> vacations as $v ):?>
<? $business_days = \Domain\Users\VacationRepository::countBusinessDays( $v['date_from'], $v['date_to'] );?>
<tr>
<td class="left"><?= htmlspecialchars( $v['name'] . ' ' . $v['surname'] );?></td>
<td><?= htmlspecialchars( $v['date_from'] );?></td>
<td><?= htmlspecialchars( $v['date_to'] );?></td>
<td>
<span class="vacation-type-badge type-<?= htmlspecialchars( $v['type'] );?>">
<?= isset( $this -> vacation_types[ $v['type'] ] ) ? $this -> vacation_types[ $v['type'] ] : $v['type'];?>
</span>
</td>
<td class="center"><?= $business_days;?></td>
<td class="left"><?= htmlspecialchars( $v['comment'] );?></td>
<td class="center">
<button class="btn btn-success btn_small vacation-edit"
data-id="<?= (int) $v['id'];?>"
data-user-id="<?= (int) $v['user_id'];?>"
data-date-from="<?= htmlspecialchars( $v['date_from'] );?>"
data-date-to="<?= htmlspecialchars( $v['date_to'] );?>"
data-type="<?= htmlspecialchars( $v['type'] );?>"
data-comment="<?= htmlspecialchars( $v['comment'] );?>">
<i class="fa fa-pencil"></i>
</button>
<button class="btn btn-danger btn_small vacation-delete" data-id="<?= (int) $v['id'];?>">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
<? endforeach; else:?>
<tr>
<td colspan="7" class="center">Brak nieobecno&#347;ci w wybranym okresie.</td>
</tr>
<? endif;?>
</tbody>
</table>
</div>
</div>
<script>
(function(){
var currentYear = <?= (int) $this -> year;?>;
var currentFilterUser = <?= (int) $this -> filter_user_id;?>;
var csrfToken = '<?= \S::csrf_token();?>';
function reloadPage() {
var url = '/users/vacations/?year=' + currentYear;
if ( currentFilterUser ) url += '&user_id=' + currentFilterUser;
window.location.href = url;
}
// Dodaj urlop — popup
$( '#vacation-add-btn' ).on( 'click', function(e) {
e.preventDefault();
var html = '<form id="vacation-add-form" style="padding: 15px;">';
html += '<input type="hidden" name="csrf_token" value="' + csrfToken + '">';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Pracownik</label>';
html += '<select name="user_id" class="form-control" required>';
<? if ( is_array( $this -> users ) ): foreach ( $this -> users as $u ):?>
html += '<option value="<?= (int) $u['id'];?>"><?= htmlspecialchars( $u['name'] . ' ' . $u['surname'] );?></option>';
<? endforeach; endif;?>
html += '</select></div>';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Typ</label>';
html += '<select name="type" class="form-control" required>';
<? foreach ( $this -> vacation_types as $key => $label ):?>
html += '<option value="<?= $key;?>"><?= $label;?></option>';
<? endforeach;?>
html += '</select></div>';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Data od</label>';
html += '<input type="date" name="date_from" class="form-control" required>';
html += '</div>';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Data do</label>';
html += '<input type="date" name="date_to" class="form-control" required>';
html += '</div>';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Komentarz</label>';
html += '<textarea name="comment" class="form-control" rows="2"></textarea>';
html += '</div>';
html += '<button type="submit" class="btn btn-success" style="width: 100%;"><i class="fa fa-check"></i> Zapisz</button>';
html += '</form>';
$( '.default_popup .title' ).text( 'Dodaj urlop / nieobecność' );
show_default_popup( html );
});
// Submit formularza dodawania
$( 'body' ).on( 'submit', '#vacation-add-form', function(e) {
e.preventDefault();
var $form = $( this );
$.ajax({
url: '/users/vacation_add/',
type: 'POST',
data: $form.serialize(),
success: function( response ) {
var data = typeof response === 'string' ? jQuery.parseJSON( response ) : response;
if ( data.status === 'success' ) {
$( '.default_popup .close' ).click();
$.alert({ title: 'Sukces', content: data.msg, type: 'green', buttons: { ok: { action: function(){ reloadPage(); } } } });
} else {
$.alert({ title: 'Błąd', content: data.msg, type: 'red' });
}
}
});
});
// Edycja urlopu — popup
$( 'body' ).on( 'click', '.vacation-edit', function(e) {
e.preventDefault();
var $btn = $( this );
var id = $btn.data( 'id' );
var userId = $btn.data( 'user-id' );
var dateFrom = $btn.data( 'date-from' );
var dateTo = $btn.data( 'date-to' );
var type = $btn.data( 'type' );
var comment = $btn.data( 'comment' ) || '';
var html = '<form id="vacation-edit-form" style="padding: 15px;">';
html += '<input type="hidden" name="csrf_token" value="' + csrfToken + '">';
html += '<input type="hidden" name="id" value="' + id + '">';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Pracownik</label>';
html += '<select name="user_id" class="form-control" required>';
<? if ( is_array( $this -> users ) ): foreach ( $this -> users as $u ):?>
html += '<option value="<?= (int) $u['id'];?>"' + ( userId == <?= (int) $u['id'];?> ? ' selected' : '' ) + '><?= htmlspecialchars( $u['name'] . ' ' . $u['surname'] );?></option>';
<? endforeach; endif;?>
html += '</select></div>';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Typ</label>';
html += '<select name="type" class="form-control" required>';
<? foreach ( $this -> vacation_types as $key => $label ):?>
html += '<option value="<?= $key;?>"' + ( type === '<?= $key;?>' ? ' selected' : '' ) + '><?= $label;?></option>';
<? endforeach;?>
html += '</select></div>';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Data od</label>';
html += '<input type="date" name="date_from" class="form-control" value="' + dateFrom + '" required>';
html += '</div>';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Data do</label>';
html += '<input type="date" name="date_to" class="form-control" value="' + dateTo + '" required>';
html += '</div>';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Komentarz</label>';
html += '<textarea name="comment" class="form-control" rows="2">' + $('<div/>').text(comment).html() + '</textarea>';
html += '</div>';
html += '<button type="submit" class="btn btn-success" style="width: 100%;"><i class="fa fa-check"></i> Zapisz zmiany</button>';
html += '</form>';
$( '.default_popup .title' ).text( 'Edytuj urlop / nieobecność' );
show_default_popup( html );
});
// Submit formularza edycji
$( 'body' ).on( 'submit', '#vacation-edit-form', function(e) {
e.preventDefault();
var $form = $( this );
$.ajax({
url: '/users/vacation_edit/',
type: 'POST',
data: $form.serialize(),
success: function( response ) {
var data = typeof response === 'string' ? jQuery.parseJSON( response ) : response;
if ( data.status === 'success' ) {
$( '.default_popup .close' ).click();
$.alert({ title: 'Sukces', content: data.msg, type: 'green', buttons: { ok: { action: function(){ reloadPage(); } } } });
} else {
$.alert({ title: 'Błąd', content: data.msg, type: 'red' });
}
}
});
});
// Usunięcie urlopu
$( 'body' ).on( 'click', '.vacation-delete', function(e) {
e.preventDefault();
var id = $( this ).data( 'id' );
$.confirm({
title: 'Potwierdź',
content: 'Na pewno chcesz usunąć tę nieobecność?',
type: 'orange',
closeIcon: true,
closeIconClass: 'fa fa-close',
typeAnimated: true,
animation: 'opacity',
boxWidth: '500px',
useBootstrap: false,
theme: 'material',
buttons: {
confirm: {
text: 'Usuń',
btnClass: 'btn-red',
action: function(){
$.ajax({
type: 'POST',
url: '/users/vacation_delete/',
data: { id: id, csrf_token: csrfToken },
success: function( response ) {
var data = typeof response === 'string' ? jQuery.parseJSON( response ) : response;
if ( data.status === 'success' ) {
$.alert({ title: 'Sukces', content: data.msg, type: 'green', buttons: { ok: { action: function(){ reloadPage(); } } } });
}
}
});
}
},
cancel: {
text: 'Anuluj',
btnClass: 'btn-default',
action: function(){}
}
}
});
});
// Zmiana limitu
$( 'body' ).on( 'click', '.vacation-change-limit', function(e) {
e.preventDefault();
var userId = $( this ).data( 'user-id' );
var currentLimit = $( this ).data( 'current-limit' );
var html = '<form id="vacation-limit-form" style="padding: 15px;">';
html += '<input type="hidden" name="csrf_token" value="' + csrfToken + '">';
html += '<input type="hidden" name="user_id" value="' + userId + '">';
html += '<input type="hidden" name="year" value="' + currentYear + '">';
html += '<div class="form-group" style="margin-bottom: 12px;">';
html += '<label>Limit dni urlopowych na rok ' + currentYear + '</label>';
html += '<input type="number" name="days_limit" class="form-control" min="0" max="365" value="' + currentLimit + '" required>';
html += '</div>';
html += '<button type="submit" class="btn btn-success" style="width: 100%;"><i class="fa fa-check"></i> Zapisz</button>';
html += '</form>';
$( '.default_popup .title' ).text( 'Zmień limit urlopowy' );
show_default_popup( html );
});
// Submit formularza limitu
$( 'body' ).on( 'submit', '#vacation-limit-form', function(e) {
e.preventDefault();
var $form = $( this );
$.ajax({
url: '/users/vacation_limit_save/',
type: 'POST',
data: $form.serialize(),
success: function( response ) {
var data = typeof response === 'string' ? jQuery.parseJSON( response ) : response;
if ( data.status === 'success' ) {
$( '.default_popup .close' ).click();
$.alert({ title: 'Sukces', content: data.msg, type: 'green', buttons: { ok: { action: function(){ reloadPage(); } } } });
} else {
$.alert({ title: 'Błąd', content: data.msg, type: 'red' });
}
}
});
});
})();
</script>