Merge branch 'main' into #22-vacation-calendar

This commit is contained in:
Adrian Hopek
2022-02-07 12:31:27 +01:00
44 changed files with 925 additions and 134 deletions

View File

@@ -82,6 +82,29 @@
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="position"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Stanowisko
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<input
id="position"
v-model="form.position"
type="text"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.position, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.position }"
>
<p
v-if="form.errors.position"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.position }}
</p>
</div>
</div>
<Listbox
v-model="form.role"
as="div"
@@ -270,6 +293,7 @@ export default {
email: null,
employmentForm: props.employmentForms[0],
role: props.roles[0],
position: null,
employmentDate: null,
})

View File

@@ -82,6 +82,29 @@
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="position"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Stanowisko
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<input
id="position"
v-model="form.position"
type="text"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.position, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.position }"
>
<p
v-if="form.errors.position"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.position }}
</p>
</div>
</div>
<Listbox
v-model="form.role"
as="div"
@@ -272,6 +295,7 @@ export default {
lastName: props.user.lastName,
email: props.user.email,
role: props.roles.find(role => role.value === props.user.role),
position: props.user.position,
employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm),
employmentDate: props.user.employmentDate,
})

View File

@@ -49,6 +49,12 @@
>
Rola
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Stanowisko
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
@@ -97,6 +103,9 @@
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.role }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.position }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.employmentForm }}
</td>

View File

@@ -10,8 +10,26 @@
class="border-t border-gray-200 px-6"
@submit.prevent="createForm"
>
<div
v-if="form.errors.vacationRequest"
class="rounded-md bg-red-50 p-4 mt-2"
>
<div class="flex">
<div class="flex-shrink-0">
<XCircleIcon class="h-5 w-5 text-red-400" />
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">
Wniosek nie mógł zostać utworzony
</h3>
<div class="mt-2 text-sm text-red-700">
<span>{{ form.errors.vacationRequest }}</span>
</div>
</div>
</div>
</div>
<Listbox
v-model="form.vacationType"
v-model="form.type"
as="div"
class="sm:grid sm:grid-cols-3 py-4 items-center"
>
@@ -21,9 +39,9 @@
<div class="mt-1 relative sm:mt-0 sm:col-span-2">
<ListboxButton
class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.vacationType, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.vacationType }"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.type, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.type }"
>
<span class="block truncate">{{ form.vacationType.label }}</span>
<span class="block truncate">{{ form.type.label }}</span>
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon class="h-5 w-5 text-gray-400" />
</span>
@@ -38,15 +56,15 @@
class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
>
<ListboxOption
v-for="vacationType in vacationTypes"
:key="vacationType.value"
v-for="type in vacationTypes"
:key="type.value"
v-slot="{ active, selected }"
as="template"
:value="vacationType"
:value="type"
>
<li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']">
<span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
{{ vacationType.label }}
{{ type.label }}
</span>
<span
@@ -60,10 +78,10 @@
</ListboxOptions>
</transition>
<p
v-if="form.errors.vacationType"
v-if="form.errors.type"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.vacationType }}
{{ form.errors.type }}
</p>
</div>
</Listbox>
@@ -77,18 +95,18 @@
<div class="mt-1 sm:mt-0 sm:col-span-2">
<FlatPickr
id="date_from"
v-model="form.dateFrom"
v-model="form.from"
:config="fromInputConfig"
placeholder="Wybierz datę"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.dateFrom, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.dateFrom }"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.from, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.from }"
@on-change="onFromChange"
/>
<p
v-if="form.errors.dateFrom"
v-if="form.errors.from"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.dateFrom }}
{{ form.errors.from }}
</p>
</div>
</div>
@@ -102,25 +120,25 @@
<div class="mt-1 sm:mt-0 sm:col-span-2">
<FlatPickr
id="date_to"
v-model="form.dateTo"
v-model="form.to"
:config="toInputConfig"
placeholder="Wybierz datę"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.dateTo, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.dateTo }"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.to, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.to }"
@on-change="onToChange"
/>
<p
v-if="form.errors.dateTo"
v-if="form.errors.to"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.dateTo }}
{{ form.errors.to }}
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<span class="block text-sm font-medium text-gray-700 sm:mt-px">Liczba dni urlopu</span>
<div class="mt-1 sm:mt-0 sm:col-span-2 w-full max-w-lg bg-gray-50 border border-gray-300 rounded-md px-4 py-2 inline-flex items-center text-gray-500 sm:text-sm">
1
{{ estimatedDays.length }}
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
@@ -135,7 +153,7 @@
id="comment"
v-model="form.comment"
rows="4"
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full max-w-lg sm:text-sm border-gray-300 rounded-md"
class="shadow-sm focus:ring-blumilk-500 focus:border-blumilk-500 block w-full max-w-lg sm:text-sm border-gray-300 rounded-md"
/>
</div>
</div>
@@ -164,8 +182,9 @@
import {useForm} from '@inertiajs/inertia-vue3'
import FlatPickr from 'vue-flatpickr-component'
import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue'
import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid'
import {reactive} from 'vue'
import {CheckIcon, SelectorIcon, XCircleIcon} from '@heroicons/vue/solid'
import {reactive, ref} from 'vue'
import axios from 'axios'
export default {
name: 'VacationRequestCreate',
@@ -178,6 +197,7 @@ export default {
ListboxOptions,
CheckIcon,
SelectorIcon,
XCircleIcon,
},
props: {
vacationTypes: {
@@ -191,12 +211,14 @@ export default {
},
setup(props) {
const form = useForm({
dateFrom: null,
dateTo: null,
vacationType: props.vacationTypes[0],
from: null,
to: null,
type: props.vacationTypes[0],
comment: null,
})
const estimatedDays = ref([])
const disableDates = [
date => (date.getDay() === 0 || date.getDay() === 6),
]
@@ -213,6 +235,7 @@ export default {
return {
form,
estimatedDays,
fromInputConfig,
toInputConfig,
}
@@ -221,18 +244,26 @@ export default {
createForm() {
this.form
.transform(data => ({
from: data.dateFrom,
to: data.dateTo,
type: data.vacationType.value,
comment: data.comment,
...data,
type: data.type.value,
}))
.post('/vacation-requests')
},
onFromChange(selectedDates, dateStr) {
this.toInputConfig.minDate = dateStr
this.refreshEstimatedDays(this.form.from, this.form.to)
},
onToChange(selectedDates, dateStr) {
this.fromInputConfig.maxDate = dateStr
this.refreshEstimatedDays(this.form.from, this.form.to)
},
refreshEstimatedDays(from, to) {
if (from && to) {
axios.post('/api/calculate-vacation-days', {from, to})
.then(res => this.estimatedDays = res.data)
}
},
},

View File

@@ -33,40 +33,40 @@
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Numer
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Rodzaj urlopu
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Status
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Od
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Do
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
class="px-4 py-3 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Dni urlopu
</th>
<th
scope="col"
class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Status
</th>
<th scope="col" />
</tr>
</thead>
@@ -87,17 +87,17 @@
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ request.type }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ request.state }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ request.from }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ request.to }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-right text-sm text-gray-500">
{{ request.estimatedDays }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
X
{{ request.state }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
<InertiaLink :href="`/vacation-requests/${request.id}`">

View File

@@ -44,10 +44,10 @@
</div>
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
Dni
Dni urlopu
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
x
{{ request.estimatedDays }}
</dd>
</div>
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
@@ -58,6 +58,30 @@
{{ request.comment }}
</dd>
</div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
Załączniki
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
<ul class="border border-gray-200 rounded-md divide-y divide-gray-200">
<li class="pl-3 pr-4 py-3 flex items-center justify-between text-sm">
<div class="w-0 flex-1 flex items-center">
<PaperClipIcon class="flex-shrink-0 h-5 w-5 text-gray-400" />
<span class="ml-2 flex-1 w-0 truncate"> wniosek_urlopowy.pdf </span>
</div>
<div class="ml-4 flex-shrink-0">
<a
:href="`/vacation-requests/${request.id}/download`"
target="_blank"
class="font-medium text-blumilk-600 hover:text-blumilk-500"
>
Pobierz
</a>
</div>
</li>
</ul>
</dd>
</div>
</dl>
</div>
</div>
@@ -196,11 +220,13 @@
<script>
import { ThumbUpIcon } from '@heroicons/vue/outline'
import {PaperClipIcon} from '@heroicons/vue/solid'
export default {
name: 'VacationRequestShow',
components: {
ThumbUpIcon,
PaperClipIcon,
},
props: {
request: {

View File

@@ -16,8 +16,8 @@
"sick_vacation": "Zwolnienie lekarskie",
"employee": "Pracownik",
"administrator": "Administrator",
"technical_approver": "Techniczny klepacz",
"administrative_approver": "Administracyjny klepacz",
"technical_approver": "Techniczny akceptujący",
"administrative_approver": "Administracyjny akceptujący",
"created": "Utworzony",
"canceled": "Anulowany",
"rejected": "Odrzucony",
@@ -25,5 +25,10 @@
"waiting_for_technical": "Czeka na akceptację od technicznego",
"waiting_for_administrative": "Czeka na akceptację od administracyjnego",
"accepted_by_technical": "Zaakceptowany przez technicznego",
"accepted_by_administrative": "Zaakceptowany przez administracyjnego"
"accepted_by_administrative": "Zaakceptowany przez administracyjnego",
"You have pending vacation request in this range.": "Masz oczekujący wniosek urlopowy w tym zakresie dat.",
"You have approved vacation request in this range.": "Masz zaakceptowany wniosek urlopowy w tym zakresie dat.",
"You have exceeded your vacation limit.": "Przekroczyłeś/aś limit urlopu.",
"Vacation needs minimum one day.": "Urlop musi być co najmniej na jeden dzień.",
"The vacation request cannot be created at the turn of the year.": "Wniosek urlopowy nie może zostać złożony na przełomie roku."
}

View File

@@ -114,4 +114,15 @@ return [
"uploaded" => "Nie udało się wgrać pliku :attribute.",
"url" => "Format pola :attribute jest nieprawidłowy.",
"uuid" => "Pole :attribute musi być poprawnym identyfikatorem UUID.",
"attributes" => [
"to" => "do",
"from" => "od",
"firstName" => "imię",
"lastName" => "nazwisko",
"email" => "e-mail",
"position" => "stanowisko",
"employmentDate" => "data zatrudnienia",
"date" => "data",
"name" => "nazwa",
],
];

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Wniosek urlopowy</title>
<style>
body {
font-family: DejaVu Sans, sans-serif;
}
h2 {
text-align: center;
}
p {
margin: 0;
text-align: center;
}
.container {
margin: 60px 20px;
}
.helper-text {
font-size: 12px;
font-weight: bold;
padding-bottom: 12px;
}
.content {
margin-top: 60px;
line-height: 24px;
text-align: left;
}
.main {
margin-top: 60px;
}
table {
width: 100%;
text-align: center;
}
.signatureTable {
margin-top: 100px;
}
</style>
</head>
<body>
<div class="container">
<div>
<table>
<tbody>
<tr>
<td>{{ $vacationRequest->user->fullName }}</td>
<td>Legnica, {{ $vacationRequest->created_at->format("d.m.Y") }}</td>
</tr>
<tr>
<td class="helper-text">imię i nazwisko</td>
</tr>
<tr>
<td>{{ $vacationRequest->user->position }}</td>
</tr>
<tr>
<td class="helper-text">stanowisko</td>
</tr>
</tbody>
</table>
</div>
<div class="main">
<h2>Wniosek o urlop</h2>
<p class="content">
Proszę o {{ mb_strtolower($vacationRequest->type->label()) }} w okresie od dnia {{ $vacationRequest->from->format("d.m.Y") }}
do dnia {{ $vacationRequest->to->format("d.m.Y") }} włącznie tj. {{ $vacationRequest->estimated_days }} dni roboczych za rok {{ $vacationRequest->yearPeriod->year }}.
</p>
</div>
<table class="signatureTable">
<tbody>
<tr>
<td>........................</td>
<td>........................</td>
</tr>
<tr>
<td class="helper-text">podpis przełożonego</td>
<td class="helper-text">podpis pracownika</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>