Skip to content

Commit

Permalink
#377 - calendar months dropdown (#468)
Browse files Browse the repository at this point in the history
* #377 - refactor: refactored calendar month buttons - added dropdown with months

* #377 - feat: added changing year from calendar buttons

* #377 - fix: fixed icons

* #377 - fix: linter fixes

* #377 - fix: js linter node version update

* #377 - fix: js linter node version update 2

* #377 - fix: js linter node version update 3

* #377 - feat: added tests
  • Loading branch information
kamilpiech97 authored Jul 22, 2024
1 parent 54ad119 commit 1b1264b
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 182 deletions.
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();
}
}

0 comments on commit 1b1264b

Please sign in to comment.