Skip to content

Commit

Permalink
#354 - wip
Browse files Browse the repository at this point in the history
  • Loading branch information
EwelinaSkrzypacz committed Oct 5, 2023
1 parent 8024176 commit 86f02dd
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 18 deletions.
15 changes: 0 additions & 15 deletions app/Domain/DailySummaryRetriever.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,6 @@ public function getUpcomingRemoteDays(Carbon $date): Collection
->get();
}

/**
* @return Collection<User>
*/
public function getBirthdays(Carbon $date): Collection
{
return User::query()
->whereRelation(
"profile",
fn(Builder $query): Builder => $query
->whereMonth("birthday", $date->month)
->whereDay("birthday", $date->day),
)
->get();
}

/**
* @return Collection<User>
*/
Expand Down
28 changes: 25 additions & 3 deletions app/Eloquent/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ public function scopeSearch(Builder $query, ?string $text): Builder
);
}

public function scopeOrderByProfileField(Builder $query, string $field): Builder
public function scopeOrderByProfileField(Builder $query, string $field, string $direction = 'asc'): Builder
{
$profileQuery = Profile::query()->select($field)->whereColumn("users.id", "profiles.user_id");

return $query->orderBy($profileQuery);
return $query->orderBy($profileQuery, $direction);
}

public function scopeWithVacationLimitIn(Builder $query, YearPeriod $yearPeriod): Builder
Expand All @@ -133,8 +133,12 @@ public function scopeStatus(Builder $query, ?string $status): Builder
};
}

public function upcomingBirthday(): Carbon
public function upcomingBirthday(): ?Carbon
{
if (!$this->profile->birthday) {
return null;
}

$today = Carbon::today();

$birthday = $this->profile->birthday->setYear($today->year);
Expand All @@ -146,11 +150,29 @@ public function upcomingBirthday(): Carbon
return $birthday;
}

public function seniority(): string
{
return $this->profile->employment_date->longAbsoluteDiffForHumans(Carbon::today(), 2);
}

public function routeNotificationForSlack()
{
return $this->profile->slack_id;
}

public function scopeSortForEmployeesMilestones(Builder $query, ?string $sort): Builder
{
return match ($sort) {
// "birthday-asc" TO DO
// "birthday-desc" TO DO
"seniority-asc" => $query->orderByProfileField('employment_date', 'asc'),
"seniority-desc" => $query->orderByProfileField('employment_date', 'desc'),
default => $query
->orderByProfileField("last_name")
->orderByProfileField("first_name"),
};
}

protected static function newFactory(): UserFactory
{
return UserFactory::new();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Toby\Infrastructure\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Response;
use Toby\Eloquent\Models\User;
use Toby\Infrastructure\Http\Resources\EmployeeMilestoneResource;

class EmployeesMilestonesController extends Controller
{
public function index(Request $request): Response
{
$searchText = $request->query("search");
$sort = $request->query("sort");

$users = User::query()
->sortForEmployeesMilestones($sort)
->search($searchText)
->orderByProfileField("last_name")
->orderByProfileField("first_name")
->paginate()
->withQueryString();

return inertia("EmployeesMilestones", [
"users" => EmployeeMilestoneResource::collection($users),
"filters" => [
"search" => $searchText,
],
]);
}
}
32 changes: 32 additions & 0 deletions app/Infrastructure/Http/Resources/EmployeeMilestoneResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Toby\Infrastructure\Http\Resources;

use Carbon\CarbonInterface;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Carbon;

class EmployeeMilestoneResource extends JsonResource
{
public static $wrap = null;

public function toArray($request): array
{
$upcomingBirthday = $this->upcomingBirthday();
$seniority = $this->seniority();

return [
"user" => new SimpleUserResource($this->resource),
"birthdayDisplayDate" => $upcomingBirthday?->toDisplayString(),
"birthdayRelativeDate" => $upcomingBirthday?->isToday()
? __("today")
: $upcomingBirthday?->diffForHumans(
Carbon::today(),
["options" => CarbonInterface::ONE_DAY_WORDS, "syntax" => CarbonInterface::DIFF_RELATIVE_TO_NOW],
),
"seniorityDisplayDate" => $seniority,
];
}
}
206 changes: 206 additions & 0 deletions resources/js/Pages/EmployeesMilestones.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<script setup>
import Pagination from '@/Shared/Pagination.vue'
import EmptyState from '@/Shared/Feedbacks/EmptyState.vue'
import { MagnifyingGlassIcon, ChevronDownIcon } from '@heroicons/vue/24/outline'
import { reactive, watch } from 'vue'
import { debounce } from 'lodash'
import { Inertia } from '@inertiajs/inertia'
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/24/solid'
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue'
const sortOptions = [
{
name: 'Sortowanie domyślne',
value: null,
},
{
name: 'Urodziny od najbliższej daty',
value: 'birthday-asc',
},
{
name: 'Urodziny od najdalszej daty',
value: 'birthday-desc',
},
{
name: 'Najdłuższy staż pracy',
value: 'seniority-asc',
},
{
name: 'Najkrótszy staż pracy',
value: 'seniority-desc',
},
]
const props = defineProps({
users: Object,
filters: Object,
})
const form = reactive({
search: props.filters.search,
sort: sortOptions.find(item => item.value === props.filters.sort) ?? sortOptions[0],
})
watch(form, debounce(() => {
Inertia.get('/employees-milestones', {
search: form.search,
sort: form.sort?.value ?? undefined,
}, {
preserveState: true,
replace: true,
})
}, 300))
</script>
<template>
<InertiaHead title="Jubileusze" />
<div class="bg-white shadow-md">
<div class="flex justify-between items-center p-4 sm:px-6">
<div>
<h2 class="text-lg font-medium leading-6 text-gray-900">
Jubileusze
</h2>
</div>
</div>
<div class="border-t border-gray-200">
<div class="flex-1 grid grid-cols-1 p-4 md:grid-cols-3 gap-4">
<div class="relative">
<div class="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center">
<MagnifyingGlassIcon class="w-5 h-5 text-gray-400" />
</div>
<input
v-model.trim="form.search"
type="search"
class="block py-2 pr-3 pl-10 w-full max-w-lg placeholder:text-gray-500 focus:text-gray-900 focus:placeholder:text-gray-400 bg-white rounded-md border border-gray-300 focus:border-blumilk-500 focus:outline-none focus:ring-1 focus:ring-blumilk-500 sm:text-sm"
placeholder="Szukaj"
>
</div>
<Listbox
v-model="form.sort"
as="div"
>
<div class="relative mt-1 sm:mt-0">
<ListboxButton
class="relative py-2 pr-10 pl-3 w-full max-w-lg text-left bg-white rounded-md border border-gray-300 focus:border-blumilk-500 focus:outline-none focus:ring-1 focus:ring-blumilk-500 shadow-sm cursor-default sm:text-sm"
>
<span class="flex items-center">
{{ form.sort.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>
</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-full 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="option in sortOptions"
:key="option.value"
v-slot="{ active, selected }"
as="template"
:value="option"
>
<li
:class="[active ? 'bg-gray-100' : 'text-gray-900', 'cursor-default truncate select-none relative py-2 pl-3 pr-9']"
>
{{ option.name }}
<span
v-if="selected"
:class="['text-blumilk-600 absolute inset-y-0 right-0 flex items-center pr-4']"
>
<CheckIcon class="w-5 h-5" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</div>
<div class="overflow-auto xl:overflow-visible relative">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
>
<span>
Imię i nazwisko
</span>
</th>
<th
scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
>
Następne urodziny
</th>
<th
scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
>
Staż pracy
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
<tr
v-for="user in users.data"
>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap">
<div class="flex">
<span class="inline-flex justify-center items-center w-10 h-10 rounded-full">
<img
class="w-10 h-10 rounded-full"
:src="user.user.avatar"
>
</span>
<div class="ml-3">
<p class="text-sm font-medium text-gray-900 break-all">
{{ user.user.name }}
</p>
<p class="text-sm text-gray-500 break-all">
{{ user.user.email }}
</p>
</div>
</div>
</td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ user.birthdayDisplayDate }} - {{ user.birthdayRelativeDate }}
</td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ user.seniorityDisplayDate }}
</td>
</tr>
<tr
v-if="! users.data.length"
>
<td
colspan="100%"
class="py-4 text-xl leading-5 text-center text-gray-700"
>
<EmptyState>
<template #title>
Nie znaleziono użytkownika
</template>
<template #text>
Spróbuj sformułować zapytanie inaczej
</template>
</EmptyState>
</td>
</tr>
</tbody>
</table>
</div>
<Pagination :pagination="users.meta" />
</div>
</div>
</template>
9 changes: 9 additions & 0 deletions resources/js/Shared/MainMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import {
GiftIcon,
BanknotesIcon,
DocumentDuplicateIcon,
CakeIcon,
} from '@heroicons/vue/24/outline'
import { CheckIcon, ChevronDownIcon } from '@heroicons/vue/24/solid'
import EmployeesMilestones from "@/Pages/EmployeesMilestones.vue";
const props = defineProps({
auth: Object,
Expand Down Expand Up @@ -101,6 +103,13 @@ const miscNavigation = computed(() => [
icon: UserGroupIcon,
can: props.auth.can.manageUsers,
},
{
name: 'Jubileusze',
href: '/employees-milestones',
section: 'EmployeesMilestones',
icon: CakeIcon,
can: true,
},
{
name: 'Klucze',
href: '/keys',
Expand Down
4 changes: 4 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Toby\Infrastructure\Http\Controllers\BenefitController;
use Toby\Infrastructure\Http\Controllers\BenefitsReportController;
use Toby\Infrastructure\Http\Controllers\DashboardController;
use Toby\Infrastructure\Http\Controllers\EmployeesMilestonesController;
use Toby\Infrastructure\Http\Controllers\GoogleController;
use Toby\Infrastructure\Http\Controllers\HolidayController;
use Toby\Infrastructure\Http\Controllers\KeysController;
Expand Down Expand Up @@ -85,6 +86,9 @@
Route::post("/keys/{key}/give", [KeysController::class, "give"]);
Route::post("/keys/{key}/leave-in-the-office", [KeysController::class, "leaveInTheOffice"]);

Route::get("/employees-milestones", [EmployeesMilestonesController::class, "index"])
->name("employees-milestones");

Route::post("/year-periods/{yearPeriod}/select", SelectYearPeriodController::class)
->whereNumber("yearPeriod")
->name("year-periods.select");
Expand Down

0 comments on commit 86f02dd

Please sign in to comment.