- vue composition api (#91)

* wip

* fix

Co-authored-by: EwelinaLasowy <ewelina.lasowy@blumilk.pl>
This commit is contained in:
Adrian Hopek 2022-03-22 15:03:42 +01:00 committed by GitHub
parent 95f5ed44d6
commit dcda8c6255
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 938 additions and 1466 deletions

View File

@ -13,5 +13,7 @@ module.exports = {
indent: ['error', 2], indent: ['error', 2],
'vue/html-indent': ['error', 2], 'vue/html-indent': ['error', 2],
'comma-dangle': ['error', 'always-multiline'], 'comma-dangle': ['error', 'always-multiline'],
'object-curly-spacing': ['error', 'always'],
'vue/require-default-prop': 0,
}, },
} }

View File

@ -65,7 +65,7 @@ const types = [
}, },
] ]
export function useVacationTypeInfo() { export default function useVacationTypeInfo() {
const getTypes = () => types const getTypes = () => types
const findType = value => types.find(type => type.value === value) const findType = value => types.find(type => type.value === value)

View File

@ -1,5 +1,5 @@
import {computed} from 'vue' import { computed } from 'vue'
import {usePage} from '@inertiajs/inertia-vue3' import { usePage } from '@inertiajs/inertia-vue3'
export default function useCurrentYearPeriodInfo() { export default function useCurrentYearPeriodInfo() {
const minDate = computed(() => new Date(usePage().props.value.years.current, 0, 1)) const minDate = computed(() => new Date(usePage().props.value.years.current, 0, 1))

View File

@ -83,9 +83,7 @@
v-for="user in users.data" v-for="user in users.data"
:key="user.id" :key="user.id"
> >
<th <th class="border border-gray-300 py-2 px-4">
class="border border-gray-300 py-2 px-4"
>
<div class="flex justify-start items-center"> <div class="flex justify-start items-center">
<span class="inline-flex items-center justify-center h-10 w-10 rounded-full"> <span class="inline-flex items-center justify-center h-10 w-10 rounded-full">
<img <img
@ -94,9 +92,7 @@
> >
</span> </span>
<div class="ml-3"> <div class="ml-3">
<div <div class="text-sm font-medium text-gray-900 whitespace-nowrap">
class="text-sm font-medium text-gray-900 whitespace-nowrap"
>
{{ user.name }} {{ user.name }}
</div> </div>
</div> </div>
@ -122,66 +118,28 @@
</div> </div>
</template> </template>
<script> <script setup>
import {ChevronLeftIcon, ChevronRightIcon} from '@heroicons/vue/solid' import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/vue/solid'
import {computed} from 'vue' import { computed } from 'vue'
import {useMonthInfo} from '@/Composables/monthInfo' import { useMonthInfo } from '@/Composables/monthInfo'
import VacationTypeCalendarIcon from '@/Shared/VacationTypeCalendarIcon' import VacationTypeCalendarIcon from '@/Shared/VacationTypeCalendarIcon'
export default { const props = defineProps({
name: 'VacationCalendar', users: Object,
components: { auth: Object,
VacationTypeCalendarIcon, calendar: Object,
ChevronLeftIcon, current: String,
ChevronRightIcon, selected: String,
}, years: Object,
props: { can: Object,
users: { })
type: Object,
default: () => null,
},
auth: {
type: Object,
default: () => null,
},
calendar: {
type: Object,
default: () => null,
},
current: {
type: String,
default: () => 'january',
},
selected: {
type: String,
default: () => 'january',
},
years: {
type: Object,
default: () => null,
},
can: {
type: Object,
default: () => null,
},
},
setup(props) {
const {getMonths, findMonth} = useMonthInfo()
const months = getMonths()
const currentMonth = computed(() => findMonth(props.current)) const { getMonths, findMonth } = useMonthInfo()
const selectedMonth = computed(() => findMonth(props.selected))
const previousMonth = computed(() => months[months.indexOf(selectedMonth.value) - 1])
const nextMonth = computed(() => months[months.indexOf(selectedMonth.value) + 1])
const months = getMonths()
return { const currentMonth = computed(() => findMonth(props.current))
months, const selectedMonth = computed(() => findMonth(props.selected))
currentMonth, const previousMonth = computed(() => months[months.indexOf(selectedMonth.value) - 1])
selectedMonth, const nextMonth = computed(() => months[months.indexOf(selectedMonth.value) + 1])
previousMonth,
nextMonth,
}
},
}
</script> </script>

View File

@ -2,346 +2,38 @@
<InertiaHead title="Strona główna" /> <InertiaHead title="Strona główna" />
<div class="grid grid-cols-1 gap-4 items-start lg:grid-cols-3 lg:gap-8"> <div class="grid grid-cols-1 gap-4 items-start lg:grid-cols-3 lg:gap-8">
<div class="grid grid-cols-1 gap-4 lg:col-span-2"> <div class="grid grid-cols-1 gap-4 lg:col-span-2">
<section> <Welcome :user="auth.user" />
<div class=" bg-white overflow-hidden shadow"> <VacationStats :stats="stats" />
<div class="bg-white p-6">
<div class="sm:flex sm:items-center sm:justify-between">
<div class="sm:flex sm:space-x-5">
<div class="flex-shrink-0">
<img
class="mx-auto h-20 w-20 rounded-full"
:src="user.avatar"
>
</div>
<div class="mt-4 text-center sm:mt-0 sm:pt-1 sm:text-left">
<p class="text-sm font-medium text-gray-600">
Cześć,
</p>
<p class="text-xl font-bold text-gray-900 sm:text-2xl">
{{ user.name }}
</p>
<p class="text-sm font-medium text-gray-600">
{{ user.role }}
</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section>
<div class="grid grid-cols-2 gap-4">
<div class="bg-white shadow-md p-4">
<VacationChart :stats="stats" />
</div>
<div class="h-full">
<div class="grid grid-cols-2 gap-4 h-full">
<div class="px-4 py-5 bg-white shadow-md sm:p-6">
<dd class="mt-1 text-4xl font-semibold text-blumilk-500">
{{ stats.remaining }}
</dd>
<dt class="text-md font-medium text-gray-700 truncate">
Pozostało
</dt>
<dt class="text-sm font-medium text-gray-500 mt-2">
Dni do wykorzystania teraz.
</dt>
</div>
<div class="px-4 py-5 bg-white shadow-md sm:p-6">
<dd class="mt-1 text-4xl font-semibold text-blumilk-700">
{{ stats.used }}
</dd>
<dt class="text-md font-medium text-gray-700 truncate">
Dni wykorzystane
</dt>
<dt class="text-sm font-medium text-gray-500 mt-2">
Dni, które zostały już wykorzystane na urlop wypoczynkowy.
</dt>
</div>
<div class="px-4 py-5 bg-white shadow-md sm:p-6">
<dt class="mt-1 text-4xl font-semibold text-blumilk-200">
{{ stats.pending }}
</dt>
<dd class="text-md font-medium text-gray-500 truncate">
Rozpatrywane
</dd>
<dt class="text-sm font-medium text-gray-500 mt-2">
Dni czekające na akceptację przełożonych.
</dt>
</div>
<div class="px-4 py-5 bg-white shadow-md sm:p-6">
<dt class="mt-1 text-4xl font-semibold text-gray-900">
{{ stats.limit }}
</dt>
<dd class="text-md font-medium text-gray-500 truncate">
Limit urlopu
</dd>
<dt class="text-sm font-medium text-gray-500 mt-2">
Twój roczny limit urlopu wypoczynkowego.
</dt>
</div>
<div class="px-4 py-5 bg-white shadow-md sm:p-6 col-span-2">
<dt class="mt-1 text-4xl font-semibold text-gray-900">
{{ stats.other }}
</dt>
<dd class="text-md font-medium text-gray-500 truncate">
Inne urlopy
</dd>
<dt class="text-sm font-medium text-gray-500 mt-2">
Urlopy bezpłatne, okolicznościowe, zwolnienia lekarskie, itd., które zostały już zatwierdzone.
</dt>
</div>
</div>
</div>
</div>
</section>
</div> </div>
<div class="grid grid-cols-1 gap-4"> <div class="grid grid-cols-1 gap-4">
<section v-if="can.listAllVacationRequests"> <PendingVacationRequests
<div class="bg-white shadow-md"> v-if="can.listAllVacationRequests"
<div class="p-4 sm:px-6"> :requests="vacationRequests.data"
<h2 class="text-lg leading-6 font-medium text-gray-900"> />
Wnioski oczekujące na akcje <UserVacationRequests
</h2> v-else
</div> :requests="vacationRequests.data"
<div class="border-t border-gray-200 pb-5 px-4 sm:px-6"> />
<div class="flow-root mt-6"> <AbsenceList :absences="absences.data" />
<ul class="-my-5 divide-y divide-gray-200"> <UpcomingHolidays :holidays="holidays.data" />
<li
v-for="request in vacationRequests.data"
:key="request.id"
class="py-5"
>
<div class="relative focus-within:ring-2 focus-within:ring-blumilk-500">
<h3 class="text-sm font-semibold text-blumilk-600 hover:text-blumilk-500">
<InertiaLink
:href="`/vacation-requests/${request.id}`"
class="hover:underline focus:outline-none"
>
<span class="absolute inset-0" />
Wniosek o {{ findType(request.type).text.toLowerCase() }}
[{{ request.name }}]
</InertiaLink>
</h3>
<p class="mt-1 text-sm text-gray-600">
{{ request.from }} - {{ request.to }}
</p>
<div class="mt-3 text-sm text-gray-600">
<div class="flex">
<img
class="h-10 w-10 rounded-full"
:src="request.user.avatar"
>
<div class="ml-3">
<p class="text-sm font-medium text-gray-900">
{{ request.user.name }}
</p>
<p class="text-sm text-gray-500">
{{ request.user.email }}
</p>
</div>
</div>
</div>
</div>
</li>
<li v-if="! vacationRequests.data.length">
<p class="py-2">
Brak danych
</p>
</li>
</ul>
</div>
<div class="mt-6">
<InertiaLink
href="/vacation-requests"
:data="{status: 'waiting_for_action'}"
class="w-full flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blumilk-500"
>
Zobacz wszystkie
</InertiaLink>
</div>
</div>
</div>
</section>
<section v-else>
<div class="bg-white shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Twoje wnioski
</h2>
</div>
<div class="border-t border-gray-200 pb-5 px-4 sm:px-6">
<div class="flow-root mt-6">
<ul class="-my-5 divide-y divide-gray-200">
<li
v-for="request in vacationRequests.data"
:key="request.id"
class="py-5"
>
<div class="relative focus-within:ring-2 focus-within:ring-blumilk-500">
<h3 class="text-sm font-semibold text-blumilk-600 hover:text-blumilk-500">
<InertiaLink
:href="`/vacation-requests/${request.id}`"
class="hover:underline focus:outline-none"
>
<span class="absolute inset-0" />
Wniosek o {{ findType(request.type).text.toLowerCase() }}
[{{ request.name }}]
</InertiaLink>
</h3>
<p class="mt-1 text-sm text-gray-600">
{{ request.from }} - {{ request.to }}
</p>
<p class="mt-2 text-sm text-gray-600">
<Status :status="request.state" />
</p>
</div>
</li>
<li v-if="! vacationRequests.data.length">
<p class="py-2">
Brak danych
</p>
</li>
</ul>
</div>
<div class="mt-6">
<InertiaLink
href="/vacation-requests/me"
class="w-full flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blumilk-500"
>
Zobacz wszystkie
</InertiaLink>
</div>
</div>
</div>
</section>
<section>
<div class="bg-white shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Dzisiejsze nieobecności
</h2>
</div>
<div class="border-t border-gray-200 px-4 sm:px-6">
<ul class="divide-y divide-gray-200">
<li
v-for="absence in absences.data"
:key="absence.user.id"
class="py-4 flex"
>
<img
class="h-10 w-10 rounded-full"
:src="absence.user.avatar"
>
<div class="ml-3">
<p class="text-sm font-medium text-gray-900">
{{ absence.user.name }}
</p>
<p class="text-sm text-gray-500">
{{ absence.user.email }}
</p>
</div>
</li>
<li v-if="! absences.data.length">
<p class="py-2">
Brak danych
</p>
</li>
</ul>
</div>
</div>
</section>
<section>
<div class="bg-white shadow-md">
<div>
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Najbliższe dni wolne
</h2>
</div>
<div class="border-t border-gray-200 px-4 pb-5 sm:px-6">
<ul class="divide-y divide-gray-200">
<li
v-for="holiday in holidays.data"
:key="holiday.id.id"
class="py-4 flex"
>
<div>
<p class="text-sm font-medium text-gray-900">
{{ holiday.name }}
</p>
<p class="text-sm text-gray-500">
{{ holiday.displayDate }}
</p>
</div>
</li>
<li v-if="! holidays.data.length">
<p class="py-2">
Brak danych
</p>
</li>
</ul>
<div>
<InertiaLink
href="/holidays"
class="w-full flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blumilk-500"
>
Zobacz wszystkie
</InertiaLink>
</div>
</div>
</div>
</div>
</section>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup>
import {computed} from 'vue' import Welcome from '@/Shared/Widgets/Welcome'
import {usePage} from '@inertiajs/inertia-vue3' import VacationStats from '@/Shared/Widgets/VacationStats'
import Status from '@/Shared/Status' import AbsenceList from '@/Shared/Widgets/AbsenceList'
import VacationChart from '@/Shared/VacationChart' import UpcomingHolidays from '@/Shared/Widgets/UpcomingHolidays'
import {useVacationTypeInfo} from '@/Composables/vacationTypeInfo' import UserVacationRequests from '@/Shared/Widgets/UserVacationRequests'
import PendingVacationRequests from '@/Shared/Widgets/PendingVacationRequests'
export default { defineProps({
name: 'DashboardPage', auth: Object,
components: {Status, VacationChart}, absences: Object,
props: { vacationRequests: Object,
absences: { holidays: Object,
type: Object, can: Object,
default: null, stats: Object,
}, })
vacationRequests: {
type: Object,
default: null,
},
holidays: {
type: Object,
default: null,
},
can: {
type: Object,
default: null,
},
stats: {
type: Object,
default: () => ({
used: 0,
pending: 0,
remaining: 0,
}),
},
},
setup() {
const user = computed(() => usePage().props.value.auth.user)
const { findType } = useVacationTypeInfo()
return {
user,
findType,
}
},
}
</script> </script>

View File

@ -81,34 +81,19 @@
</div> </div>
</template> </template>
<script> <script setup>
import {useForm} from '@inertiajs/inertia-vue3' import { useForm } from '@inertiajs/inertia-vue3'
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
import useCurrentYearPeriodInfo from '@/Composables/yearPeriodInfo' import useCurrentYearPeriodInfo from '@/Composables/yearPeriodInfo'
export default { const form = useForm({
name: 'HolidayCreate',
components: {
FlatPickr,
},
setup() {
const form = useForm({
name: null, name: null,
date: null, date: null,
}) })
const {minDate, maxDate} = useCurrentYearPeriodInfo() const { minDate, maxDate } = useCurrentYearPeriodInfo()
return { function createHoliday() {
form, form.post('/holidays')
minDate,
maxDate,
}
},
methods: {
createHoliday() {
this.form.post('/holidays')
},
},
} }
</script> </script>

View File

@ -80,34 +80,20 @@
</div> </div>
</template> </template>
<script> <script setup>
import { useForm } from '@inertiajs/inertia-vue3' import { useForm } from '@inertiajs/inertia-vue3'
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
export default { const props = defineProps({
name: 'HolidayEdit', holiday: Object,
components: { })
FlatPickr,
}, const form = useForm({
props: {
holiday: {
type: Object,
default: () => null,
},
},
setup(props) {
const form = useForm({
name: props.holiday.name, name: props.holiday.name,
date: props.holiday.date, date: props.holiday.date,
}) })
return { form } function editHoliday() {
}, form.put(`/holidays/${props.holiday.id}`)
methods: {
editHoliday() {
this.form
.put(`/holidays/${this.holiday.id}`)
},
},
} }
</script> </script>

View File

@ -94,10 +94,7 @@
:href="`/holidays/${holiday.id}/edit`" :href="`/holidays/${holiday.id}/edit`"
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'font-medium block px-4 py-2 text-sm']" :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'font-medium block px-4 py-2 text-sm']"
> >
<PencilIcon <PencilIcon class="mr-2 h-5 w-5 text-blue-500" /> Edytuj
class="mr-2 h-5 w-5 text-blue-500"
aria-hidden="true"
/> Edytuj
</InertiaLink> </InertiaLink>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
@ -111,10 +108,7 @@
:href="`/holidays/${holiday.id}`" :href="`/holidays/${holiday.id}`"
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']"
> >
<TrashIcon <TrashIcon class="mr-2 h-5 w-5 text-red-500" /> Usuń
class="mr-2 h-5 w-5 text-red-500"
aria-hidden="true"
/> Usuń
</InertiaLink> </InertiaLink>
</MenuItem> </MenuItem>
</div> </div>
@ -123,9 +117,7 @@
</Menu> </Menu>
</td> </td>
</tr> </tr>
<tr <tr v-if="!holidays.data.length">
v-if="!holidays.data.length"
>
<td <td
colspan="100%" colspan="100%"
class="text-center py-4 text-xl leading-5 text-gray-700" class="text-center py-4 text-xl leading-5 text-gray-700"
@ -140,33 +132,12 @@
</div> </div>
</template> </template>
<script> <script setup>
import { DotsVerticalIcon, PencilIcon, TrashIcon } from '@heroicons/vue/solid' import { DotsVerticalIcon, PencilIcon, TrashIcon } from '@heroicons/vue/solid'
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue' import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
export default { defineProps({
name: 'HolidayIndex', holidays: Object,
components: { can: Object,
DotsVerticalIcon, })
PencilIcon,
TrashIcon,
Menu,
MenuButton,
MenuItem,
MenuItems,
},
props: {
holidays: {
type: Object,
default: () => null,
},
can: {
type: Object,
default: () => null,
},
},
setup() {
return {}
},
}
</script> </script>

View File

@ -26,10 +26,7 @@
@click="delete errors.oauth" @click="delete errors.oauth"
> >
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
<XIcon <XIcon class="h-5 w-5" />
class="h-5 w-5"
aria-hidden="true"
/>
</button> </button>
</div> </div>
</div> </div>
@ -43,7 +40,6 @@
<img <img
class="mx-auto h-50 w-auto" class="mx-auto h-50 w-auto"
src="img/logo.png" src="img/logo.png"
alt="Blumilk"
> >
<a <a
href="/login/google/start" href="/login/google/start"
@ -64,23 +60,16 @@
</div> </div>
</template> </template>
<script setup>
import { XIcon, ExclamationIcon } from '@heroicons/vue/solid'
defineProps({
errors: Object,
})
</script>
<script> <script>
import GuestLayout from '@/Shared/Layout/GuestLayout' import GuestLayout from '@/Shared/Layout/GuestLayout'
import {XIcon} from '@heroicons/vue/solid'
import {ExclamationIcon} from '@heroicons/vue/solid'
export default { export default { name: 'LoginPage', layout: GuestLayout }
name: 'LoginPage',
components: {
XIcon,
ExclamationIcon,
},
layout: GuestLayout,
props: {
errors: {
type: Object,
default: () => ({oauth: null}),
},
},
}
</script> </script>

View File

@ -258,36 +258,18 @@
</div> </div>
</template> </template>
<script> <script setup>
import { useForm } from '@inertiajs/inertia-vue3' import { useForm } from '@inertiajs/inertia-vue3'
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue' import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue'
import { CheckIcon, SelectorIcon } from '@heroicons/vue/solid' import { CheckIcon, SelectorIcon } from '@heroicons/vue/solid'
export default { const props = defineProps({
name: 'UserCreate', employmentForms: Object,
components: { roles: Object,
FlatPickr, })
Listbox,
ListboxButton, const form = useForm({
ListboxLabel,
ListboxOption,
ListboxOptions,
CheckIcon,
SelectorIcon,
},
props: {
employmentForms: {
type: Object,
default: () => null,
},
roles: {
type: Object,
default: () => null,
},
},
setup(props) {
const form = useForm({
firstName: null, firstName: null,
lastName: null, lastName: null,
email: null, email: null,
@ -295,20 +277,15 @@ export default {
role: props.roles[0], role: props.roles[0],
position: null, position: null,
employmentDate: null, employmentDate: null,
}) })
return { form } function createUser() {
}, form
methods: {
createUser() {
this.form
.transform(data => ({ .transform(data => ({
...data, ...data,
employmentForm: data.employmentForm.value, employmentForm: data.employmentForm.value,
role: data.role.value, role: data.role.value,
})) }))
.post('/users') .post('/users')
},
},
} }
</script> </script>

View File

@ -128,7 +128,9 @@
leave-from-class="opacity-100" leave-from-class="opacity-100"
leave-to-class="opacity-0" leave-to-class="opacity-0"
> >
<ListboxOptions 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"> <ListboxOptions
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 <ListboxOption
v-for="role in roles" v-for="role in roles"
:key="role.value" :key="role.value"
@ -136,7 +138,9 @@
as="template" as="template"
:value="role" :value="role"
> >
<li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> <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']"> <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
{{ role.label }} {{ role.label }}
</span> </span>
@ -182,7 +186,9 @@
leave-from-class="opacity-100" leave-from-class="opacity-100"
leave-to-class="opacity-0" leave-to-class="opacity-0"
> >
<ListboxOptions 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"> <ListboxOptions
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 <ListboxOption
v-for="employmentForm in employmentForms" v-for="employmentForm in employmentForms"
:key="employmentForm.value" :key="employmentForm.value"
@ -190,7 +196,9 @@
as="template" as="template"
:value="employmentForm" :value="employmentForm"
> >
<li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> <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']"> <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
{{ employmentForm.label }} {{ employmentForm.label }}
</span> </span>
@ -257,40 +265,19 @@
</div> </div>
</template> </template>
<script> <script setup>
import {useForm} from '@inertiajs/inertia-vue3' import { useForm } from '@inertiajs/inertia-vue3'
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue' import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue'
import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid' import { CheckIcon, SelectorIcon } from '@heroicons/vue/solid'
export default { const props = defineProps({
name: 'UserEdit', employmentForms: Object,
components: { roles: Object,
FlatPickr, user: Object,
Listbox, })
ListboxButton,
ListboxLabel, const form = useForm({
ListboxOption,
ListboxOptions,
CheckIcon,
SelectorIcon,
},
props: {
employmentForms: {
type: Object,
default: () => null,
},
roles: {
type: Object,
default: () => null,
},
user: {
type: Object,
default: () => null,
},
},
setup(props) {
const form = useForm({
firstName: props.user.firstName, firstName: props.user.firstName,
lastName: props.user.lastName, lastName: props.user.lastName,
email: props.user.email, email: props.user.email,
@ -298,20 +285,15 @@ export default {
position: props.user.position, position: props.user.position,
employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm), employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm),
employmentDate: props.user.employmentDate, employmentDate: props.user.employmentDate,
}) })
return { form } function editUser() {
}, form
methods: {
editUser() {
this.form
.transform(data => ({ .transform(data => ({
...data, ...data,
employmentForm: data.employmentForm.value, employmentForm: data.employmentForm.value,
role: data.role.value, role: data.role.value,
})) }))
.put(`/users/${this.user.id}`) .put(`/users/${props.user.id}`)
},
},
} }
</script> </script>

View File

@ -81,13 +81,10 @@
> >
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
<div class="flex"> <div class="flex">
<span <span class="inline-flex items-center justify-center h-10 w-10 rounded-full">
class="inline-flex items-center justify-center h-10 w-10 rounded-full"
>
<img <img
class="h-10 w-10 rounded-full" class="h-10 w-10 rounded-full"
:src="user.avatar" :src="user.avatar"
alt=""
> >
</span> </span>
<div class="ml-3"> <div class="ml-3">
@ -118,10 +115,7 @@
class="relative inline-block text-left" class="relative inline-block text-left"
> >
<MenuButton class="rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blumilk-500"> <MenuButton class="rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blumilk-500">
<DotsVerticalIcon <DotsVerticalIcon class="h-5 w-5" />
class="h-5 w-5"
aria-hidden="true"
/>
</MenuButton> </MenuButton>
<transition <transition
@ -145,10 +139,7 @@
:href="`/users/${user.id}/edit`" :href="`/users/${user.id}/edit`"
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'font-medium block px-4 py-2 text-sm']" :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'font-medium block px-4 py-2 text-sm']"
> >
<PencilIcon <PencilIcon class="mr-2 h-5 w-5 text-blue-500" /> Edytuj
class="mr-2 h-5 w-5 text-blue-500"
aria-hidden="true"
/> Edytuj
</InertiaLink> </InertiaLink>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
@ -162,10 +153,7 @@
:href="`/users/${user.id}`" :href="`/users/${user.id}`"
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']"
> >
<TrashIcon <TrashIcon class="mr-2 h-5 w-5 text-red-500" /> Usuń
class="mr-2 h-5 w-5 text-red-500"
aria-hidden="true"
/> Usuń
</InertiaLink> </InertiaLink>
</MenuItem> </MenuItem>
</div> </div>
@ -184,10 +172,7 @@
:href="`/users/${user.id}/restore`" :href="`/users/${user.id}/restore`"
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']"
> >
<RefreshIcon <RefreshIcon class="mr-2 h-5 w-5 text-green-500" /> Przywróć
class="mr-2 h-5 w-5 text-green-500"
aria-hidden="true"
/> Przywróć
</InertiaLink> </InertiaLink>
</MenuItem> </MenuItem>
</div> </div>
@ -208,102 +193,32 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <Pagination :pagination="users.meta" />
v-if="users.data.length && users.meta.last_page !== 1"
class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6 rounded-b-lg"
>
<div class="flex-1 flex justify-between sm:hidden">
<InertiaLink
:is="users.links.prev ? 'InertiaLink': 'span'"
:href="users.links.prev"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Poprzednia
</InertiaLink>
<Component
:is="users.links.next ? 'InertiaLink': 'span'"
:href="users.links.next"
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Następna
</Component>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div class="text-sm text-gray-700">
Wyświetlanie
<span class="font-medium">{{ users.meta.from }}</span>
od
<span class="font-medium">{{ users.meta.to }}</span>
do
<span class="font-medium">{{ users.meta.total }}</span>
wyników
</div>
<nav class="relative z-0 inline-flex space-x-1">
<template
v-for="(link, index) in users.meta.links"
:key="index"
>
<Component
:is="link.url ? 'InertiaLink' : 'span'"
:href="link.url"
:preserve-scroll="true"
class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium"
:class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}"
v-text="link.label"
/>
</template>
</nav>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { Inertia } from '@inertiajs/inertia' import { Inertia } from '@inertiajs/inertia'
import { debounce } from 'lodash' import { debounce } from 'lodash'
import { SearchIcon } from '@heroicons/vue/outline' import { SearchIcon } from '@heroicons/vue/outline'
import { DotsVerticalIcon, PencilIcon, TrashIcon, RefreshIcon } from '@heroicons/vue/solid' import { DotsVerticalIcon, PencilIcon, TrashIcon, RefreshIcon } from '@heroicons/vue/solid'
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue' import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
import Pagination from '@/Shared/Pagination'
export default { const props = defineProps({
name: 'UserIndex', users: Object,
components: { filters: Object,
SearchIcon, })
DotsVerticalIcon,
PencilIcon,
TrashIcon,
RefreshIcon,
Menu,
MenuButton,
MenuItem,
MenuItems,
},
props: {
users: {
type: Object,
default: () => null,
},
filters: {
type: Object,
default: () => null,
},
},
setup(props) {
let search = ref(props.filters.search)
watch(search, debounce(value => { const search = ref(props.filters.search)
Inertia.get('/users', value ? { search: value} : {}, {
watch(search, debounce(value => {
Inertia.get('/users', value ? { search: value } : {}, {
preserveState: true, preserveState: true,
replace: true, replace: true,
}) })
}, 300)) }, 300))
return {
search,
}
},
}
</script> </script>

View File

@ -7,7 +7,7 @@
Dostępne dni urlopu dla użytkowników Dostępne dni urlopu dla użytkowników
</h2> </h2>
<p class="mt-1 text-sm text-gray-500"> <p class="mt-1 text-sm text-gray-500">
Zarządzaj dostepnymi dniami urlopów dla użytkowników. Zarządzaj dostępnymi dniami urlopów dla użytkowników.
</p> </p>
</div> </div>
</div> </div>
@ -56,13 +56,10 @@
> >
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
<div class="flex"> <div class="flex">
<span <span class="inline-flex items-center justify-center h-10 w-10 rounded-full">
class="inline-flex items-center justify-center h-10 w-10 rounded-full"
>
<img <img
class="h-10 w-10 rounded-full" class="h-10 w-10 rounded-full"
:src="item.user.avatar" :src="item.user.avatar"
alt=""
> >
</span> </span>
<div class="ml-3"> <div class="ml-3">
@ -112,9 +109,7 @@
</div> </div>
</td> </td>
</tr> </tr>
<tr <tr v-if="!form.items.length">
v-if="!form.items.length"
>
<td <td
colspan="100%" colspan="100%"
class="text-center py-4 text-xl leading-5 text-gray-700" class="text-center py-4 text-xl leading-5 text-gray-700"
@ -137,37 +132,21 @@
</div> </div>
</template> </template>
<script> <script setup>
import {Switch} from '@headlessui/vue' import { Switch } from '@headlessui/vue'
import {useForm} from '@inertiajs/inertia-vue3' import { useForm } from '@inertiajs/inertia-vue3'
export default { const props = defineProps({
name: 'VacationLimits', limits: Object,
components: { years: Object,
Switch, })
},
props: { const form = useForm({
limits: {
type: Object,
default: () => null,
},
years: {
type: Object,
default: () => null,
},
},
setup(props) {
const form = useForm({
items: props.limits, items: props.limits,
}) })
return { function submitVacationDays() {
form, form
}
},
methods: {
submitVacationDays() {
this.form
.transform(data => ({ .transform(data => ({
items: data.items.map(item => ({ items: data.items.map(item => ({
id: item.id, id: item.id,
@ -178,7 +157,5 @@ export default {
preserveState: (page) => Object.keys(page.props.errors).length, preserveState: (page) => Object.keys(page.props.errors).length,
preserveScroll: true, preserveScroll: true,
}) })
},
},
} }
</script> </script>

View File

@ -322,55 +322,26 @@
</div> </div>
</template> </template>
<script> <script setup>
import {useForm} from '@inertiajs/inertia-vue3' import { useForm } from '@inertiajs/inertia-vue3'
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions, Switch} from '@headlessui/vue' import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions, Switch } from '@headlessui/vue'
import {CheckIcon, SelectorIcon, XCircleIcon} from '@heroicons/vue/solid' import { CheckIcon, SelectorIcon, XCircleIcon } from '@heroicons/vue/solid'
import {reactive, ref, watch} from 'vue' import { reactive, ref, watch } from 'vue'
import axios from 'axios' import axios from 'axios'
import useCurrentYearPeriodInfo from '@/Composables/yearPeriodInfo' import useCurrentYearPeriodInfo from '@/Composables/yearPeriodInfo'
import VacationChart from '@/Shared/VacationChart' import VacationChart from '@/Shared/VacationChart'
export default {
name: 'VacationRequestCreate', const props = defineProps({
components: { auth: Object,
VacationChart, users: Object,
Switch, vacationTypes: Object,
FlatPickr, holidays: Object,
Listbox, can: Object,
ListboxButton, })
ListboxLabel,
ListboxOption, const form = useForm({
ListboxOptions,
CheckIcon,
SelectorIcon,
XCircleIcon,
},
props: {
auth: {
type: Object,
default: () => null,
},
users: {
type: Object,
default: () => null,
},
vacationTypes: {
type: Object,
default: () => null,
},
holidays: {
type: Object,
default: () => null,
},
can: {
type: Object,
default: () => null,
},
},
setup(props) {
const form = useForm({
user: props.can.createOnBehalfOfEmployee user: props.can.createOnBehalfOfEmployee
? props.users.data.find(user => user.id === props.auth.user.id) ?? props.users.data[0] ? props.users.data.find(user => user.id === props.auth.user.id) ?? props.users.data[0]
: props.auth.user, : props.auth.user,
@ -379,71 +350,66 @@ export default {
type: props.vacationTypes[0], type: props.vacationTypes[0],
comment: null, comment: null,
flowSkipped: false, flowSkipped: false,
}) })
const estimatedDays = ref([]) const estimatedDays = ref([])
const stats = ref({ const stats = ref({
used: 0, used: 0,
pending: 0, pending: 0,
remaining: 0, remaining: 0,
}) })
const {minDate, maxDate} = useCurrentYearPeriodInfo() const { minDate, maxDate } = useCurrentYearPeriodInfo()
const disableDates = [ const disableDates = [
date => (date.getDay() === 0 || date.getDay() === 6), date => (date.getDay() === 0 || date.getDay() === 6),
] ]
const fromInputConfig = reactive({ const fromInputConfig = reactive({
minDate: minDate, minDate,
maxDate: maxDate, maxDate,
disable: disableDates, disable: disableDates,
}) })
const toInputConfig = reactive({ const toInputConfig = reactive({
minDate: minDate, minDate,
maxDate: maxDate, maxDate,
disable: disableDates, disable: disableDates,
}) })
watch(() => form.user, user => { watch(() => form.user, async user => {
axios.post('/api/calculate-vacations-stats', {user: user.id}) const res = await axios.post('/api/calculate-vacations-stats', { user: user.id })
.then(res => stats.value = res.data)
}, {immediate: true})
return { stats.value = res.data
form, }, { immediate: true })
estimatedDays,
stats, function createForm() {
fromInputConfig, form
toInputConfig,
}
},
methods: {
createForm() {
this.form
.transform(data => ({ .transform(data => ({
...data, ...data,
type: data.type.value, type: data.type.value,
user: data.user.id, user: data.user.id,
})) }))
.post('/vacation-requests') .post('/vacation-requests')
},
onFromChange(selectedDates, dateStr) {
this.form.to = dateStr
this.refreshEstimatedDays(this.form.from, this.form.to)
},
onToChange() {
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)
}
},
},
} }
function onFromChange(selectedDates, dateStr) {
form.to = dateStr
refreshEstimatedDays(form.from, form.to)
}
function onToChange() {
refreshEstimatedDays(form.from, form.to)
}
async function refreshEstimatedDays(from, to) {
if (from && to) {
const res = await axios.post('/api/calculate-vacation-days', { from, to })
estimatedDays.value = res.data
}
}
</script> </script>

View File

@ -118,9 +118,7 @@
/> />
</td> </td>
</tr> </tr>
<tr <tr v-if="! requests.data.length">
v-if="! requests.data.length"
>
<td <td
colspan="100%" colspan="100%"
class="text-center py-4 text-xl leading-5 text-gray-700" class="text-center py-4 text-xl leading-5 text-gray-700"
@ -130,110 +128,24 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <Pagination :pagination="requests.meta" />
v-if="requests.data.length && requests.meta.last_page !== 1"
class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6 rounded-b-lg"
>
<div class="flex-1 flex justify-between sm:hidden">
<InertiaLink
:is="requests.links.prev ? 'InertiaLink': 'span'"
:href="requests.links.prev"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Poprzednia
</InertiaLink>
<Component
:is="requests.links.next ? 'InertiaLink': 'span'"
:href="requests.links.next"
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Następna
</Component>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div class="text-sm text-gray-700">
Wyświetlanie
<span class="font-medium">{{ requests.meta.from }}</span>
od
<span class="font-medium">{{ requests.meta.to }}</span>
do
<span class="font-medium">{{ requests.meta.total }}</span>
wyników
</div>
<nav class="relative z-0 inline-flex space-x-1">
<template
v-for="(link, index) in requests.meta.links"
:key="index"
>
<Component
:is="link.url ? 'InertiaLink' : 'span'"
:href="link.url"
:preserve-scroll="true"
class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium"
:class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}"
v-text="link.label"
/>
</template>
</nav>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup>
import { import { ChevronRightIcon } from '@heroicons/vue/solid'
ChevronRightIcon,
ClockIcon,
DotsVerticalIcon,
PencilIcon,
ThumbDownIcon,
ThumbUpIcon,
TrashIcon,
XIcon,
CheckIcon,
DocumentTextIcon,
} from '@heroicons/vue/solid'
import Status from '@/Shared/Status' import Status from '@/Shared/Status'
import VacationType from '@/Shared/VacationType' import VacationType from '@/Shared/VacationType'
import Pagination from '@/Shared/Pagination'
export default { defineProps({
name: 'VacationRequestIndex', requests: Object,
components: { stats: Object,
DotsVerticalIcon, filters: Object,
PencilIcon, })
TrashIcon,
ChevronRightIcon, const statuses = [
ThumbUpIcon,
ClockIcon,
XIcon,
CheckIcon,
DocumentTextIcon,
ThumbDownIcon,
Status,
VacationType,
},
props: {
requests: {
type: Object,
default: () => null,
},
stats: {
type: Object,
default: () => ({
all: 0,
pending: 0,
success: 0,
failed: 0,
}),
},
filters: {
type: Object,
default: () => null,
},
},
setup() {
const statuses = [
{ {
name: 'Wszystkie', name: 'Wszystkie',
value: 'all', value: 'all',
@ -250,11 +162,5 @@ export default {
name: 'Odrzucone/anulowane', name: 'Odrzucone/anulowane',
value: 'failed', value: 'failed',
}, },
] ]
return {
statuses,
}
},
}
</script> </script>

View File

@ -93,7 +93,9 @@
:src="user.avatar" :src="user.avatar"
class="flex-shrink-0 h-6 w-6 rounded-full" class="flex-shrink-0 h-6 w-6 rounded-full"
> >
<span :class="[form.user?.id === user.id ? 'font-semibold' : 'font-normal', 'ml-3 block truncate']"> <span
:class="[form.user?.id === user.id ? 'font-semibold' : 'font-normal', 'ml-3 block truncate']"
>
{{ user.name }} {{ user.name }}
</span> </span>
</div> </div>
@ -123,9 +125,7 @@
<ListboxButton <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 focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300" 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 focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300"
> >
<span <span class="flex items-center">
class="flex items-center"
>
{{ form.status.name }} {{ form.status.name }}
</span> </span>
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
@ -274,9 +274,7 @@
/> />
</td> </td>
</tr> </tr>
<tr <tr v-if="! requests.data.length">
v-if="! requests.data.length"
>
<td <td
colspan="100%" colspan="100%"
class="text-center py-4 text-xl leading-5 text-gray-700" class="text-center py-4 text-xl leading-5 text-gray-700"
@ -286,118 +284,28 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <Pagination :pagination="requests.meta" />
v-if="requests.data.length && requests.meta.last_page !== 1"
class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6 rounded-b-lg"
>
<div class="flex-1 flex justify-between sm:hidden">
<InertiaLink
:is="requests.links.prev ? 'InertiaLink': 'span'"
:href="requests.links.prev"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Poprzednia
</InertiaLink>
<Component
:is="requests.links.next ? 'InertiaLink': 'span'"
:href="requests.links.next"
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Następna
</Component>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div class="text-sm text-gray-700">
Wyświetlanie
<span class="font-medium">{{ requests.meta.from }}</span>
od
<span class="font-medium">{{ requests.meta.to }}</span>
do
<span class="font-medium">{{ requests.meta.total }}</span>
wyników
</div>
<nav class="relative z-0 inline-flex space-x-1">
<template
v-for="(link, index) in requests.meta.links"
:key="index"
>
<Component
:is="link.url ? 'InertiaLink' : 'span'"
:href="link.url"
:preserve-scroll="true"
class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium"
:class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}"
v-text="link.label"
/>
</template>
</nav>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup>
import { import { CheckIcon, ChevronRightIcon, SelectorIcon } from '@heroicons/vue/solid'
CheckIcon,
ChevronRightIcon,
ClockIcon,
DocumentTextIcon,
DotsVerticalIcon,
PencilIcon,
SelectorIcon,
ThumbDownIcon,
ThumbUpIcon,
TrashIcon,
XCircleIcon,
XIcon,
} from '@heroicons/vue/solid'
import Status from '@/Shared/Status' import Status from '@/Shared/Status'
import VacationType from '@/Shared/VacationType' import VacationType from '@/Shared/VacationType'
import {watch, reactive} from 'vue' import { watch, reactive } from 'vue'
import {debounce} from 'lodash' import { debounce } from 'lodash'
import {Inertia} from '@inertiajs/inertia' import { Inertia } from '@inertiajs/inertia'
import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue' import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue'
import Pagination from '@/Shared/Pagination'
export default { const props = defineProps({
name: 'VacationRequestIndex', requests: Object,
components: { users: Object,
Listbox, filters: Object,
ListboxButton, })
ListboxLabel,
ListboxOption, const statuses = [
ListboxOptions,
DotsVerticalIcon,
PencilIcon,
TrashIcon,
ChevronRightIcon,
ThumbUpIcon,
ClockIcon,
XIcon,
CheckIcon,
DocumentTextIcon,
ThumbDownIcon,
Status,
VacationType,
SelectorIcon,
XCircleIcon,
},
props: {
requests: {
type: Object,
default: () => null,
},
users: {
type: Object,
default: () => null,
},
filters: {
type: Object,
default: () => null,
},
},
setup(props) {
const statuses = [
{ {
name: 'Wszystkie', name: 'Wszystkie',
value: 'all', value: 'all',
@ -418,21 +326,17 @@ export default {
name: 'Odrzucone/anulowane', name: 'Odrzucone/anulowane',
value: 'failed', value: 'failed',
}, },
] ]
const form = reactive({ const form = reactive({
user: props.users.data.find(user => user.id === props.filters.user) ?? null, user: props.users.data.find(user => user.id === props.filters.user) ?? null,
status: statuses.find(status => status.value === props.filters.status) ?? statuses[0], status: statuses.find(status => status.value === props.filters.status) ?? statuses[0],
}) })
watch(form, debounce(() => { watch(form, debounce(() => {
Inertia.get('/vacation-requests', {user: form.user?.id, status: form.status.value}, { Inertia.get('/vacation-requests', { user: form.user?.id, status: form.status.value }, {
preserveState: true, preserveState: true,
replace: true, replace: true,
}) })
}, 300)) }, 300))
return {form, statuses}
},
}
</script> </script>

View File

@ -245,33 +245,15 @@
</div> </div>
</template> </template>
<script> <script setup>
import {PaperClipIcon} from '@heroicons/vue/outline' import { PaperClipIcon } from '@heroicons/vue/outline'
import Activity from '@/Shared/Activity' import Activity from '@/Shared/Activity'
import Status from '@/Shared/Status' import Status from '@/Shared/Status'
import VacationType from '@/Shared/VacationType' import VacationType from '@/Shared/VacationType'
export default { defineProps({
name: 'VacationRequestShow', request: Object,
components: { can: Object,
VacationType, activities: Object,
Activity, })
PaperClipIcon,
Status,
},
props: {
request: {
type: Object,
default: () => null,
},
can: {
type: Object,
default: () => null,
},
activities: {
type: Object,
default: () => null,
},
},
}
</script> </script>

View File

@ -31,30 +31,16 @@
</div> </div>
</template> </template>
<script> <script setup>
import {computed} from 'vue' import { computed } from 'vue'
import {useStatusInfo} from '@/Composables/statusInfo' import { useStatusInfo } from '@/Composables/statusInfo'
export default { const props = defineProps({
name: 'VacationRequestActivity', activity: Object,
props: { last: Boolean,
activity: { })
type: Object,
default: () => null,
},
last: {
type: Boolean,
default: () => false,
},
},
setup(props) {
const { findStatus } = useStatusInfo()
const statusInfo = computed(() => findStatus(props.activity.state)) const { findStatus } = useStatusInfo()
return { const statusInfo = computed(() => findStatus(props.activity.state))
statusInfo,
}
},
}
</script> </script>

View File

@ -1,6 +1,9 @@
<template> <template>
<div class="min-h-full"> <div class="min-h-full">
<MainMenu /> <MainMenu
:auth="auth"
:years="years"
/>
<main class="lg:ml-64 flex flex-col flex-1 py-8"> <main class="lg:ml-64 flex flex-col flex-1 py-8">
<div class="px-4"> <div class="px-4">
<slot /> <slot />
@ -9,26 +12,20 @@
</div> </div>
</template> </template>
<script> <script setup>
import MainMenu from '@/Shared/MainMenu' import MainMenu from '@/Shared/MainMenu'
import {useToast} from 'vue-toastification' import { useToast } from 'vue-toastification'
import {watch} from 'vue' import { defineProps, watch } from 'vue'
export default { const props = defineProps({
name: 'AppLayout', flash: Object,
components: { auth: Object,
MainMenu, years: Object,
}, })
props: {
flash: {
type: Object,
default: () => null,
},
},
setup(props) {
const toast = useToast()
watch(() => props.flash, flash => { const toast = useToast()
watch(() => props.flash, flash => {
if (flash.success) { if (flash.success) {
toast.success(flash.success) toast.success(flash.success)
} }
@ -36,11 +33,5 @@ export default {
if (flash.error) { if (flash.error) {
toast.error(flash.error) toast.error(flash.error)
} }
}, {immediate:true}) }, { immediate:true })
return {
toast,
}
},
}
</script> </script>

View File

@ -3,9 +3,3 @@
<slot /> <slot />
</div> </div>
</template> </template>
<script>
export default {
name: 'GuestLayout',
}
</script>

View File

@ -91,7 +91,6 @@
</Dialog> </Dialog>
</TransitionRoot> </TransitionRoot>
<!-- Static sidebar for desktop -->
<div class="hidden lg:flex lg:w-64 lg:flex-col lg:fixed lg:inset-y-0"> <div class="hidden lg:flex lg:w-64 lg:flex-col lg:fixed lg:inset-y-0">
<div class="flex flex-col flex-grow bg-blumilk-700 pt-5 pb-4 overflow-y-auto"> <div class="flex flex-col flex-grow bg-blumilk-700 pt-5 pb-4 overflow-y-auto">
<div class="flex items-center flex-shrink-0 px-4"> <div class="flex items-center flex-shrink-0 px-4">
@ -202,30 +201,23 @@
</div> </div>
</div> </div>
<div class="ml-4 flex items-center md:ml-6"> <div class="ml-4 flex items-center md:ml-6">
<!-- Profile dropdown -->
<Menu <Menu
as="div" as="div"
class="ml-3 relative" class="ml-3 relative"
> >
<div>
<MenuButton <MenuButton
class="max-w-xs bg-white rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500 lg:p-2 lg:rounded-md lg:hover:bg-gray-50" class="max-w-xs bg-white rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500 lg:p-2 lg:rounded-md lg:hover:bg-gray-50"
> >
<img <img
class="h-8 w-8 rounded-full" class="h-8 w-8 rounded-full"
:src="auth.user.avatar" :src="auth.user.avatar"
alt="Avatar"
> >
<span class="hidden ml-3 text-gray-700 text-sm font-medium lg:block"> <span class="hidden ml-3 text-gray-700 text-sm font-medium lg:block">
<span class="sr-only">Open user menu for </span> <span class="sr-only">Open user menu for </span>
{{ auth.user.name }} {{ auth.user.name }}
</span> </span>
<ChevronDownIcon <ChevronDownIcon class="hidden flex-shrink-0 ml-1 h-5 w-5 text-gray-400 lg:block" />
class="hidden flex-shrink-0 ml-1 h-5 w-5 text-gray-400 lg:block"
aria-hidden="true"
/>
</MenuButton> </MenuButton>
</div>
<transition <transition
enter-active-class="transition ease-out duration-100" enter-active-class="transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-95" enter-from-class="transform opacity-0 scale-95"
@ -237,18 +229,14 @@
<MenuItems <MenuItems
class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
> >
<MenuItem <MenuItem v-slot="{ active }">
v-for="item in userNavigation"
:key="item.name"
v-slot="{ active }"
>
<InertiaLink <InertiaLink
:href="item.href" href="/logout"
:method="item.method" method="POST"
:as="item.as" as="button"
:class="[active ? 'bg-gray-100' : '', 'block w-full text-left px-4 py-2 text-sm text-gray-700']" :class="[active ? 'bg-gray-100' : '', 'block w-full text-left px-4 py-2 text-sm text-gray-700']"
> >
{{ item.name }} Wyloguj się
</InertiaLink> </InertiaLink>
</MenuItem> </MenuItem>
</MenuItems> </MenuItems>
@ -260,8 +248,8 @@
</div> </div>
</template> </template>
<script> <script setup>
import {ref} from 'vue' import { computed, ref } from 'vue'
import { import {
Dialog, Dialog,
DialogOverlay, DialogOverlay,
@ -273,7 +261,6 @@ import {
TransitionRoot, TransitionRoot,
} from '@headlessui/vue' } from '@headlessui/vue'
import { import {
BellIcon,
HomeIcon, HomeIcon,
CollectionIcon, CollectionIcon,
MenuAlt1Icon, MenuAlt1Icon,
@ -283,71 +270,58 @@ import {
StarIcon, StarIcon,
CalendarIcon, DocumentTextIcon, CalendarIcon, DocumentTextIcon,
} from '@heroicons/vue/outline' } from '@heroicons/vue/outline'
import { import { CheckIcon, ChevronDownIcon } from '@heroicons/vue/solid'
CashIcon,
CheckCircleIcon,
CheckIcon,
ChevronDownIcon,
ChevronRightIcon,
OfficeBuildingIcon,
SearchIcon,
} from '@heroicons/vue/solid'
import {computed} from 'vue'
import {usePage} from '@inertiajs/inertia-vue3'
export default { const props = defineProps({
components: { auth: Object,
Dialog, years: Object,
DialogOverlay, })
Menu,
MenuButton,
MenuItem,
MenuItems,
TransitionChild,
TransitionRoot,
BellIcon,
CashIcon,
CheckCircleIcon,
ChevronDownIcon,
ChevronRightIcon,
MenuAlt1Icon,
OfficeBuildingIcon,
SearchIcon,
XIcon,
StarIcon,
HomeIcon,
CheckIcon,
UserGroupIcon,
SunIcon,
CalendarIcon,
},
setup() {
const sidebarOpen = ref(false)
const auth = computed(() => usePage().props.value.auth) const sidebarOpen = ref(false)
const years = computed(() => usePage().props.value.years)
const navigation = computed(() => const navigation = computed(() =>
[ [
{name: 'Moje wnioski', href: '/vacation-requests/me', component: 'VacationRequest/Index' , icon: DocumentTextIcon, can: true}, {
{name: 'Wnioski urlopowe', href: '/vacation-requests', component: 'VacationRequest/IndexForApprovers', icon: CollectionIcon, can: auth.value.can.listAllVacationRequests}, name: 'Moje wnioski',
{name: 'Kalendarz urlopów', href: '/vacation-calendar', component: 'Calendar', icon: CalendarIcon, can: true}, href: '/vacation-requests/me',
{name: 'Dni wolne', href: '/holidays', component: 'Holidays/Index', icon: StarIcon, can: true}, component: 'VacationRequest/Index',
{name: 'Limity urlopów', href: '/vacation-limits', component: 'VacationLimits', icon: SunIcon, can: auth.value.can.manageVacationLimits}, icon: DocumentTextIcon,
{name: 'Użytkownicy', href: '/users', component: 'Users/Index', icon: UserGroupIcon, can: auth.value.can.manageUsers}, can: true,
].filter(item => item.can))
const userNavigation = [
{name: 'Wyloguj się', href: '/logout', method: 'post', as: 'button'},
]
return {
auth,
years,
navigation,
userNavigation,
sidebarOpen,
}
}, },
} {
name: 'Wnioski urlopowe',
href: '/vacation-requests',
component: 'VacationRequest/IndexForApprovers',
icon: CollectionIcon,
can: props.auth.can.listAllVacationRequests,
},
{
name: 'Kalendarz urlopów',
href: '/vacation-calendar',
component: 'Calendar',
icon: CalendarIcon,
can: true,
},
{
name: 'Dni wolne',
href: '/holidays',
component: 'Holidays/Index',
icon: StarIcon,
can: true,
},
{
name: 'Limity urlopów',
href: '/vacation-limits',
component: 'VacationLimits',
icon: SunIcon,
can: props.auth.can.manageVacationLimits,
},
{
name: 'Użytkownicy',
href: '/users',
component: 'Users/Index',
icon: UserGroupIcon,
can: props.auth.can.manageUsers,
},
].filter(item => item.can))
</script> </script>

View File

@ -0,0 +1,61 @@
<template>
<div
v-if="pagination.last_page !== 1"
class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6 rounded-b-lg"
>
<div class="flex-1 flex justify-between sm:hidden">
<Component
:is="prevLink ? 'InertiaLink': 'span'"
:href="prevLink"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Poprzednia
</Component>
<Component
:is="nextLink ? 'InertiaLink': 'span'"
:href="nextLink"
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Następna
</Component>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div class="text-sm text-gray-700">
Wyświetlanie
<span class="font-medium">{{ pagination.from }}</span>
od
<span class="font-medium">{{ pagination.to }}</span>
do
<span class="font-medium">{{ pagination.total }}</span>
wyników
</div>
<nav class="relative z-0 inline-flex space-x-1">
<template
v-for="(link, index) in pagination.links"
:key="index"
>
<Component
:is="link.url ? 'InertiaLink' : 'span'"
:href="link.url"
:preserve-scroll="true"
class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium"
:class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}"
v-text="link.label"
/>
</template>
</nav>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
pagination: Object,
})
const prevLink = computed(() => props.pagination.links.at(0)?.url)
const nextLink = computed(() => props.pagination.links.at(-1)?.url)
</script>

View File

@ -2,36 +2,21 @@
<div class="flex items-center"> <div class="flex items-center">
<component <component
:is="statusInfo.solid.icon" :is="statusInfo.solid.icon"
:class="[statusInfo.solid.color ,'w-5 h-5 mr-1']" :class="[statusInfo.solid.color, 'w-5 h-5 mr-1']"
/> />
<span>{{ statusInfo.text }}</span> <span>{{ statusInfo.text }}</span>
</div> </div>
</template> </template>
<script> <script setup>
import {computed} from 'vue' import { computed } from 'vue'
import {useStatusInfo} from '@/Composables/statusInfo' import { useStatusInfo } from '@/Composables/statusInfo'
export default { const props = defineProps({
name: 'VacationRequestStatus', status: String,
props: { })
status: {
type: String,
default: () => null,
},
last: {
type: Boolean,
default: () => false,
},
},
setup(props) {
const { findStatus } = useStatusInfo()
const statusInfo = computed(() => findStatus(props.status)) const { findStatus } = useStatusInfo()
return { const statusInfo = computed(() => findStatus(props.status))
statusInfo,
}
},
}
</script> </script>

View File

@ -6,17 +6,13 @@
/> />
</template> </template>
<script> <script setup>
import { use } from 'echarts/core' import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers' import { CanvasRenderer } from 'echarts/renderers'
import { PieChart } from 'echarts/charts' import { PieChart } from 'echarts/charts'
import { import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
TitleComponent,
TooltipComponent,
LegendComponent,
} from 'echarts/components'
import VChart from 'vue-echarts' import VChart from 'vue-echarts'
import {computed} from 'vue' import { computed } from 'vue'
use([ use([
CanvasRenderer, CanvasRenderer,
@ -26,12 +22,7 @@ use([
LegendComponent, LegendComponent,
]) ])
export default { const props = defineProps({
name: 'VacationChart',
components: {
VChart,
},
props: {
stats: { stats: {
type: Object, type: Object,
default: () => ({ default: () => ({
@ -40,9 +31,9 @@ export default {
remaining: 0, remaining: 0,
}), }),
}, },
}, })
setup(props) {
const option = computed(() => ({ const option = computed(() => ({
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)', formatter: '{a} <br/>{b} : {c} ({d}%)',
@ -85,9 +76,5 @@ export default {
radius: ['30%', '70%'], radius: ['30%', '70%'],
}, },
], ],
})) }))
return { option }
},
}
</script> </script>

View File

@ -9,30 +9,15 @@
</div> </div>
</template> </template>
<script> <script setup>
import {computed} from 'vue' import { computed } from 'vue'
import {useVacationTypeInfo} from '@/Composables/vacationTypeInfo' import useVacationTypeInfo from '@/Composables/vacationTypeInfo'
export default { const props = defineProps({
name: 'VacationType',
props: {
type: {
type: String, type: String,
default: () => null, })
},
last: {
type: Boolean,
default: () => false,
},
},
setup(props) {
const { findType } = useVacationTypeInfo()
const vacationTypeInfo = computed(() => findType(props.type)) const { findType } = useVacationTypeInfo()
return { const vacationTypeInfo = computed(() => findType(props.type))
vacationTypeInfo,
}
},
}
</script> </script>

View File

@ -15,34 +15,16 @@
</Popper> </Popper>
</template> </template>
<script> <script setup>
import {computed} from 'vue' import { computed } from 'vue'
import {useVacationTypeInfo} from '@/Composables/vacationTypeInfo' import useVacationTypeInfo from '@/Composables/vacationTypeInfo'
import Popper from 'vue3-popper' import Popper from 'vue3-popper'
export default { const props = defineProps({
name: 'VacationTypeCalendarIcon',
components: {
Popper,
},
props: {
type: {
type: String, type: String,
default: () => null, })
},
last: {
type: Boolean,
default: () => false,
},
},
setup(props) {
const { findType } = useVacationTypeInfo()
const typeInfo = computed(() => findType(props.type)) const { findType } = useVacationTypeInfo()
return { const typeInfo = computed(() => findType(props.type))
typeInfo,
}
},
}
</script> </script>

View File

@ -0,0 +1,42 @@
<template>
<section class="bg-white shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Dzisiejsze nieobecności
</h2>
</div>
<div class="border-t border-gray-200 px-4 sm:px-6">
<ul class="divide-y divide-gray-200">
<li
v-for="absence in absences"
:key="absence.user.id"
class="py-4 flex"
>
<img
class="h-10 w-10 rounded-full"
:src="absence.user.avatar"
>
<div class="ml-3">
<p class="text-sm font-medium text-gray-900">
{{ absence.user.name }}
</p>
<p class="text-sm text-gray-500">
{{ absence.user.email }}
</p>
</div>
</li>
<li v-if="! absences.length">
<p class="py-2">
Brak danych
</p>
</li>
</ul>
</div>
</section>
</template>
<script setup>
defineProps({
absences: Object,
})
</script>

View File

@ -0,0 +1,76 @@
<template>
<section class="bg-white shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Wnioski oczekujące na akcje
</h2>
</div>
<div class="border-t border-gray-200 pb-5 px-4 sm:px-6">
<div class="flow-root mt-6">
<ul class="-my-5 divide-y divide-gray-200">
<li
v-for="request in requests"
:key="request.id"
class="py-5"
>
<div class="relative focus-within:ring-2 focus-within:ring-blumilk-500">
<h3 class="text-sm font-semibold text-blumilk-600 hover:text-blumilk-500">
<InertiaLink
:href="`/vacation-requests/${request.id}`"
class="hover:underline focus:outline-none"
>
<span class="absolute inset-0" />
Wniosek o {{ findType(request.type).text.toLowerCase() }}
[{{ request.name }}]
</InertiaLink>
</h3>
<p class="mt-1 text-sm text-gray-600">
{{ request.from }} - {{ request.to }}
</p>
<div class="mt-3 text-sm text-gray-600">
<div class="flex">
<img
class="h-10 w-10 rounded-full"
:src="request.user.avatar"
>
<div class="ml-3">
<p class="text-sm font-medium text-gray-900">
{{ request.user.name }}
</p>
<p class="text-sm text-gray-500">
{{ request.user.email }}
</p>
</div>
</div>
</div>
</div>
</li>
<li v-if="! requests.length">
<p class="py-2">
Brak danych
</p>
</li>
</ul>
</div>
<div class="mt-6">
<InertiaLink
href="/vacation-requests"
:data="{status: 'waiting_for_action'}"
class="w-full flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blumilk-500"
>
Zobacz wszystkie
</InertiaLink>
</div>
</div>
</section>
</template>
<script setup>
import useVacationTypeInfo from '@/Composables/vacationTypeInfo'
defineProps({
requests: Object,
})
const { findType } = useVacationTypeInfo()
</script>

View File

@ -0,0 +1,46 @@
<template>
<section class="bg-white shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Najbliższe dni wolne
</h2>
</div>
<div class="border-t border-gray-200 px-4 pb-5 sm:px-6">
<ul class="divide-y divide-gray-200">
<li
v-for="holiday in holidays"
:key="holiday.id.id"
class="py-4 flex"
>
<div>
<p class="text-sm font-medium text-gray-900">
{{ holiday.name }}
</p>
<p class="text-sm text-gray-500">
{{ holiday.displayDate }}
</p>
</div>
</li>
<li v-if="! holidays.length">
<p class="py-2">
Brak danych
</p>
</li>
</ul>
<div>
<InertiaLink
href="/holidays"
class="w-full flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blumilk-500"
>
Zobacz wszystkie
</InertiaLink>
</div>
</div>
</section>
</template>
<script setup>
defineProps({
holidays: Object,
})
</script>

View File

@ -0,0 +1,62 @@
<template>
<section class="bg-white shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Twoje wnioski
</h2>
</div>
<div class="border-t border-gray-200 pb-5 px-4 sm:px-6">
<div class="flow-root mt-6">
<ul class="-my-5 divide-y divide-gray-200">
<li
v-for="request in requests"
:key="request.id"
class="py-5"
>
<div class="relative focus-within:ring-2 focus-within:ring-blumilk-500">
<h3 class="text-sm font-semibold text-blumilk-600 hover:text-blumilk-500">
<InertiaLink
:href="`/vacation-requests/${request.id}`"
class="hover:underline focus:outline-none"
>
<span class="absolute inset-0" />
Wniosek o {{ findType(request.type).text.toLowerCase() }}
[{{ request.name }}]
</InertiaLink>
</h3>
<p class="mt-1 text-sm text-gray-600">
{{ request.from }} - {{ request.to }}
</p>
<p class="mt-2 text-sm text-gray-600">
<Status :status="request.state" />
</p>
</div>
</li>
<li v-if="! requests.length">
<p class="py-2">
Brak danych
</p>
</li>
</ul>
</div>
<div class="mt-6">
<InertiaLink
href="/vacation-requests/me"
class="w-full flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blumilk-500"
>
Zobacz wszystkie
</InertiaLink>
</div>
</div>
</section>
</template>
<script setup>
import useVacationTypeInfo from '@/Composables/vacationTypeInfo'
defineProps({
requests: Object,
})
const { findType } = useVacationTypeInfo()
</script>

View File

@ -0,0 +1,74 @@
<template>
<section class="grid grid-cols-2 gap-4">
<div class="bg-white shadow-md p-4">
<VacationChart :stats="stats" />
</div>
<div class="h-full">
<div class="grid grid-cols-2 gap-4 h-full">
<div class="px-4 py-5 bg-white shadow-md sm:p-6">
<dd class="mt-1 text-4xl font-semibold text-blumilk-500">
{{ stats.remaining }}
</dd>
<dt class="text-md font-medium text-gray-700 truncate">
Pozostało
</dt>
<dt class="text-sm font-medium text-gray-500 mt-2">
Dni do wykorzystania teraz.
</dt>
</div>
<div class="px-4 py-5 bg-white shadow-md sm:p-6">
<dd class="mt-1 text-4xl font-semibold text-blumilk-700">
{{ stats.used }}
</dd>
<dt class="text-md font-medium text-gray-700 truncate">
Dni wykorzystane
</dt>
<dt class="text-sm font-medium text-gray-500 mt-2">
Dni, które zostały już wykorzystane na urlop wypoczynkowy.
</dt>
</div>
<div class="px-4 py-5 bg-white shadow-md sm:p-6">
<dt class="mt-1 text-4xl font-semibold text-blumilk-200">
{{ stats.pending }}
</dt>
<dd class="text-md font-medium text-gray-500 truncate">
Rozpatrywane
</dd>
<dt class="text-sm font-medium text-gray-500 mt-2">
Dni czekające na akceptację przełożonych.
</dt>
</div>
<div class="px-4 py-5 bg-white shadow-md sm:p-6">
<dt class="mt-1 text-4xl font-semibold text-gray-900">
{{ stats.limit }}
</dt>
<dd class="text-md font-medium text-gray-500 truncate">
Limit urlopu
</dd>
<dt class="text-sm font-medium text-gray-500 mt-2">
Twój roczny limit urlopu wypoczynkowego.
</dt>
</div>
<div class="px-4 py-5 bg-white shadow-md sm:p-6 col-span-2">
<dt class="mt-1 text-4xl font-semibold text-gray-900">
{{ stats.other }}
</dt>
<dd class="text-md font-medium text-gray-500 truncate">
Inne urlopy
</dd>
<dt class="text-sm font-medium text-gray-500 mt-2">
Urlopy bezpłatne, okolicznościowe, zwolnienia lekarskie, itd., które zostały już zatwierdzone.
</dt>
</div>
</div>
</div>
</section>
</template>
<script setup>
import VacationChart from '@/Shared/VacationChart'
defineProps({
stats: Object,
})
</script>

View File

@ -0,0 +1,35 @@
<template>
<section>
<div class=" bg-white overflow-hidden shadow">
<div class="bg-white p-6">
<div class="sm:flex sm:items-center sm:justify-between">
<div class="sm:flex sm:space-x-5">
<div class="flex-shrink-0">
<img
class="mx-auto h-20 w-20 rounded-full"
:src="user.avatar"
>
</div>
<div class="mt-4 text-center sm:mt-0 sm:pt-1 sm:text-left">
<p class="text-sm font-medium text-gray-600">
Cześć,
</p>
<p class="text-xl font-bold text-gray-900 sm:text-2xl">
{{ user.name }}
</p>
<p class="text-sm font-medium text-gray-600">
{{ user.role }}
</p>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup>
defineProps({
user: Object,
})
</script>

View File

@ -1,6 +1,6 @@
import {createApp, h} from 'vue' import { createApp, h } from 'vue'
import {createInertiaApp, Head, Link} from '@inertiajs/inertia-vue3' import { createInertiaApp, Head, Link } from '@inertiajs/inertia-vue3'
import {InertiaProgress} from '@inertiajs/progress' import { InertiaProgress } from '@inertiajs/progress'
import AppLayout from '@/Shared/Layout/AppLayout' import AppLayout from '@/Shared/Layout/AppLayout'
import Flatpickr from 'flatpickr' import Flatpickr from 'flatpickr'
import { Polish } from 'flatpickr/dist/l10n/pl.js' import { Polish } from 'flatpickr/dist/l10n/pl.js'
@ -14,8 +14,8 @@ createInertiaApp({
return page return page
}, },
setup({el, App, props, plugin}) { setup({ el, App, props, plugin }) {
createApp({render: () => h(App, props)}) createApp({ render: () => h(App, props) })
.use(plugin) .use(plugin)
.use(Toast, { .use(Toast, {
position: 'bottom-right', position: 'bottom-right',