Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#377 - calendar months dropdown #468

Merged
merged 8 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-and-lint-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Set up node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 22
node-version: 22.4.1

- name: Instal npm dependencies
run: npm clean-install
Expand Down
32 changes: 30 additions & 2 deletions app/Http/Controllers/VacationCalendarController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@

namespace Toby\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Inertia\Response;
use Toby\Domain\CalendarGenerator;
use Toby\Enums\Month;
use Toby\Helpers\YearPeriodRetriever;
use Toby\Http\Resources\SimpleUserResource;
use Toby\Models\User;
use Toby\Models\YearPeriod;

class VacationCalendarController extends Controller
{
Expand All @@ -20,12 +23,25 @@ public function index(
YearPeriodRetriever $yearPeriodRetriever,
CalendarGenerator $calendarGenerator,
?string $month = null,
): Response {
?int $year = null,
): Response|RedirectResponse {
if ($year !== null) {
return $this->changeYearPeriod($request, $month, $year);
}

$month = Month::fromNameOrCurrent((string)$month);
$currentUser = $request->user();
$withTrashedUsers = $currentUser->canSeeInactiveUsers();

$yearPeriod = $yearPeriodRetriever->selected();
$previousYearPeriod = YearPeriod::query()
->where("year", "<", $yearPeriod->year)
->orderBy("year", "desc")
->first();
$nextYearPeriod = YearPeriod::query()
->where("year", ">", $yearPeriod->year)
->orderBy("year")
->first();
$carbonMonth = Carbon::create($yearPeriod->year, $month->toCarbonNumber());

$users = User::query()
Expand All @@ -42,9 +58,21 @@ public function index(
return inertia("Calendar", [
"calendar" => $calendar,
"current" => Month::current(),
"selected" => $month->value,
"selectedMonth" => $month->value,
"users" => SimpleUserResource::collection($users),
"withBlockedUsers" => $withTrashedUsers,
"previousYearPeriod" => $previousYearPeriod,
"nextYearPeriod" => $nextYearPeriod,
]);
}

private function changeYearPeriod(Request $request, string $month, int $year): RedirectResponse
{
$yearPeriod = YearPeriod::query()->where("year", $year)->firstOrFail();
$request->session()->put(YearPeriodRetriever::SESSION_KEY, $yearPeriod->id);
Cache::forget("selected_year_period");

return redirect()->route("calendar", ["month" => $month])
->with("info", __("Year period changed."));
}
}
1 change: 1 addition & 0 deletions lang/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"Holiday updated.": "Dzień wolny zaktualizowany.",
"Holiday deleted.": "Dzień wolny usunięty.",
"Selected year period changed.": "Wybrany rok zmieniony.",
"Year period changed.": "Zmieniono rok.",
"Vacation limits updated.": "Limity urlopów zaktualizowane.",
"Request created.": "Wniosek utworzony.",
"Request accepted.": "Wniosek zaakceptowany.",
Expand Down
302 changes: 145 additions & 157 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@heroicons/vue": "^2.1.4",
"@inertiajs/inertia": "^0.11.1",
"@inertiajs/inertia-vue3": "^0.6.0",
"@inertiajs/progress": "^0.2.7",
"@inertiajs/progress": "^0.1.2",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/line-clamp": "^0.4.4",
"@tailwindcss/typography": "^0.5.13",
Expand Down
105 changes: 85 additions & 20 deletions resources/js/Pages/Calendar.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
<script setup>
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/vue/24/solid'
import { computed, ref } from 'vue'
import { ChevronLeftIcon, ChevronRightIcon, ChevronDoubleRightIcon, ChevronDoubleLeftIcon, ChevronUpDownIcon } from '@heroicons/vue/24/solid'
import { computed, ref, watch } from 'vue'
import { useMonthInfo } from '@/Composables/monthInfo.js'
import VacationTypeCalendarIcon from '@/Shared/VacationTypeCalendarIcon.vue'
import CalendarDay from '@/Shared/CalendarDay.vue'
import UserProfileLink from '@/Shared/UserProfileLink.vue'
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/vue'
import { useForm } from '@inertiajs/inertia-vue3'
import { Inertia } from '@inertiajs/inertia'

const props = defineProps({
users: Object,
auth: Object,
calendar: Object,
current: String,
selected: String,
selectedMonth: String,
years: Object,
previousYearPeriod: Object,
nextYearPeriod: Object,
})

let activeElement = ref(undefined)
Expand All @@ -21,8 +26,17 @@ const { getMonths, findMonth } = useMonthInfo()

const months = getMonths()

const currentMonth = computed(() => findMonth(props.current))
const selectedMonth = computed(() => findMonth(props.selected))
const form = useForm({
selectedMonth: months.find(month => month.value === props.selectedMonth),
})

watch(() => form.selectedMonth, (value) => {
if (value) {
Inertia.visit(`/calendar/${value.value}`)
}
})

const selectedMonth = computed(() => findMonth(props.selectedMonth))
const previousMonth = computed(() => months[months.indexOf(selectedMonth.value) - 1])
const nextMonth = computed(() => months[months.indexOf(selectedMonth.value) + 1])

Expand Down Expand Up @@ -52,33 +66,76 @@ function linkVacationRequest(user) {
<InertiaHead title="Kalendarz" />
<div class="bg-white shadow-md">
<div class="flex-row sm:flex justify-between items-center p-4 sm:px-6">
<div class="flex items-center">
<h2 class="text-lg font-medium leading-6 text-center text-gray-900">
<div class="flex-row sm:flex items-center">
<h2 class="text-lg font-medium leading-6 sm:text-center mb-2 sm:mb-0 text-gray-900">
Kalendarz
</h2>
<div class="flex items-center ml-3 rounded-md shadow-sm md:items-stretch">
<div class="flex items-center sm:ml-3 md:items-stretch">
<InertiaLink
v-if="previousMonth"
:href="`/calendar/${previousMonth.value}`"
as="button"
class="flex focus:relative justify-center items-center p-2 text-gray-400 hover:text-gray-500 bg-white rounded-l-md border border-r-0 border-gray-300 focus:outline-blumilk-500 md:px-2 md:w-9 md:hover:bg-gray-50"
class="flex focus:relative justify-center items-center p-2 text-gray-400 hover:text-gray-500 bg-white rounded-l-md border border-r-0 border-gray-300 md:px-2 md:w-9 md:hover:bg-gray-50"
>
<ChevronLeftIcon class="w-5 h-5" />
</InertiaLink>
<InertiaLink
v-else-if="previousYearPeriod"
:href="`/calendar/${months[11].value}/${previousYearPeriod.year}`"
as="button"
class="flex focus:relative justify-center items-center p-2 text-gray-400 hover:text-gray-500 bg-white rounded-l-md border border-r-0 border-gray-300 md:px-2 md:w-9 md:hover:bg-gray-50"
>
<ChevronDoubleLeftIcon class="w-5 h-5" />
</InertiaLink>
<span
v-else
class="flex justify-center items-center p-2 text-gray-400 bg-gray-100 rounded-l-md border border-r-0 border-gray-300 md:px-2 md:w-9"
class="flex justify-center items-center text-gray-400 bg-gray-100 rounded-l-md border border-r-0 border-gray-300 md:px-2 md:w-9"
>
<ChevronLeftIcon class="w-5 h-5" />
<ChevronDoubleLeftIcon class="w-5 h-5" />
</span>
<InertiaLink
v-if="years.current.year === years.selected.year"
:href="`/calendar/${currentMonth.value}`"
as="button"
class="hidden focus:relative items-center p-2 text-sm font-medium text-gray-700 hover:text-gray-900 bg-white hover:bg-gray-50 border-y border-gray-300 focus:outline-blumilk-500 md:flex"
<Listbox
v-model="form.selectedMonth"
as="div"
class="items-center grid-cols-3 w-[135px] h-[] text-sm font-medium text-gray-700 hover:text-gray-900 bg-white hover:bg-gray-50 border-y border-gray-300 focus:outline-blumilk-500"
>
Dzisiaj
</InertiaLink>
<div class="relative sm:col-span-2 sm:mt-0">
<ListboxButton
class="relative pr-10 pl-3 w-full h-[36px] max-w-lg text-left bg-white focus:outline-none shadow-sm sm:text-sm cursor-pointer"
>
<template v-if="form.selectedMonth">
<span class="block truncate text-center">
{{ form.selectedMonth.name }}
</span>
<span class="flex absolute inset-y-0 right-0 items-center pr-2 pointer-events-none">
<ChevronUpDownIcon class="w-5 h-5 text-gray-400" />
</span>
</template>
</ListboxButton>
<transition
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="overflow-auto absolute z-10 py-1 mt-1 w-auto max-w-lg max-h-60 text-base bg-white rounded-md focus:outline-none ring-1 ring-black ring-opacity-5 shadow-lg sm:text-sm"
>
<ListboxOption
v-for="month in months"
:key="month.value"
v-slot="{ active, selected }"
:value="month"
as="template"
>
<li :class="[active ? 'bg-gray-100' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9 cursor-pointer']">
<span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
{{ month.name }}
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
<InertiaLink
v-if="nextMonth"
:href="`/calendar/${nextMonth.value}`"
Expand All @@ -87,11 +144,19 @@ function linkVacationRequest(user) {
>
<ChevronRightIcon class="w-5 h-5" />
</InertiaLink>
<InertiaLink
v-else-if="nextYearPeriod"
:href="`/calendar/${months[0].value}/${nextYearPeriod.year}`"
as="button"
class="flex focus:relative justify-center items-center p-2 text-gray-400 hover:text-gray-500 bg-white rounded-r-md border border-l-0 border-gray-300 focus:outline-blumilk-500 md:px-2 md:w-9 md:hover:bg-gray-50"
>
<ChevronDoubleRightIcon class="w-5 h-5" />
</InertiaLink>
<span
v-else
class="flex justify-center items-center p-2 text-gray-400 bg-gray-100 rounded-r-md border border-l-0 border-gray-300 md:px-2 md:w-9"
class="flex justify-center items-center text-gray-400 bg-gray-100 rounded-r-md border border-l-0 border-gray-300 md:px-2 md:w-9"
>
<ChevronRightIcon class="w-5 h-5" />
<ChevronDoubleRightIcon class="w-5 h-5" />
</span>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
->whereNumber("yearPeriod")
->name("year-periods.select");

Route::get("/calendar/{month?}", [VacationCalendarController::class, "index"])
Route::get("/calendar/{month?}/{year?}", [VacationCalendarController::class, "index"])
->name("calendar");

Route::prefix("/vacation")->as("vacation.")->group(function (): void {
Expand Down
62 changes: 62 additions & 0 deletions tests/Feature/CalendarTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Tests\Feature;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\FeatureTestCase;
use Toby\Models\User;
use Toby\Models\YearPeriod;

class CalendarTest extends FeatureTestCase
{
use DatabaseMigrations;

protected User $user;

protected function setUp(): void
{
parent::setUp();

$this->user = User::factory()->create();
}

public function testUserCanChangeYearPeriodOnTheCalendar(): void
{
$currentYearPeriod = YearPeriod::current();
$nextYearPeriod = YearPeriod::query()
->create([
"year" => $currentYearPeriod->year + 1,
]);

$this->assertNotEquals($currentYearPeriod->year, $nextYearPeriod->year);

$this->actingAs($this->user)
->get("/calendar/january/$nextYearPeriod->year")
->assertRedirect();

$selectedYearPeriod = YearPeriod::query()->where("id", session()->get("selected_year_period"))->first();
$this->assertEquals($selectedYearPeriod->year, $nextYearPeriod->year);
}

public function testUserCannotChangeYearPeriodIfYearPeriodDoesNotExist(): void
{
$currentYearPeriod = YearPeriod::current();

$this->actingAs($this->user)
->get("/calendar/january/" . ($currentYearPeriod->year + 1))
->assertStatus(404);

$this->actingAs($this->user)
->get("/calendar/january/$currentYearPeriod->year")
->assertStatus(302);
}

public function testUserCanSeeCalendar(): void
{
$this->actingAs($this->user)
->get("/calendar")
->assertOk();
}
}
Loading