#44 - vacation summary of all employees (#92)

* #44 - ui for summary

* #44 - vacation monthly usage

* #44 - fix

* #44 - fix

Co-authored-by: EwelinaLasowy <ewelina.lasowy@blumilk.pl>
This commit is contained in:
Adrian Hopek 2022-03-24 10:33:34 +01:00 committed by GitHub
parent dcda8c6255
commit 957b07b3eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 710 additions and 1019 deletions

View File

@ -31,5 +31,6 @@ class AuthServiceProvider extends ServiceProvider
Gate::define("manageHolidays", fn(User $user) => $user->role === Role::AdministrativeApprover);
Gate::define("manageVacationLimits", fn(User $user) => $user->role === Role::AdministrativeApprover);
Gate::define("generateTimesheet", fn(User $user) => $user->role === Role::AdministrativeApprover);
Gate::define("listMonthlyUsage", fn(User $user) => $user->role === Role::AdministrativeApprover);
}
}

View File

@ -5,9 +5,10 @@ declare(strict_types=1);
namespace Toby\Domain;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\VacationType;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\Vacation;
use Toby\Eloquent\Models\YearPeriod;
class UserVacationStatsRetriever
@ -30,6 +31,21 @@ class UserVacationStatsRetriever
->count();
}
public function getUsedVacationDaysByMonth(User $user, YearPeriod $yearPeriod): Collection
{
return $user->vacations()
->whereRelation(
"vacationRequest",
fn(Builder $query) => $query
->where("year_period_id", $yearPeriod->id)
->whereIn("type", $this->getLimitableVacationTypes())
->states(VacationRequestStatesRetriever::successStates()),
)
->get()
->groupBy(fn(Vacation $vacation) => strtolower($vacation->date->englishMonth))
->map(fn(Collection $items) => $items->count());
}
public function getPendingVacationDays(User $user, YearPeriod $yearPeriod): int
{
return $user

View File

@ -100,6 +100,14 @@ class User extends Authenticatable
return $this->role === $role;
}
public function hasVacationLimit(YearPeriod $yearPeriod): bool
{
return $this->vacationLimits()
->where("year_period_id", $yearPeriod->id)
->whereNotNull("days")
->exists();
}
protected function getAvatarName(): string
{
return mb_substr($this->first_name, 0, 1) . mb_substr($this->last_name, 0, 1);

View File

@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Toby\Infrastructure\Http\Controllers;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Inertia\Response;
use Toby\Domain\Enums\Month;
use Toby\Domain\UserVacationStatsRetriever;
use Toby\Eloquent\Helpers\YearPeriodRetriever;
use Toby\Eloquent\Models\User;
use Toby\Infrastructure\Http\Resources\UserResource;
class MonthlyUsageController extends Controller
{
public function __invoke(
Request $request,
YearPeriodRetriever $yearPeriodRetriever,
UserVacationStatsRetriever $statsRetriever,
): Response {
$this->authorize("listMonthlyUsage");
$currentYearPeriod = $yearPeriodRetriever->selected();
$currentUser = $request->user();
$users = User::query()
->whereRelation(
"vacationlimits",
fn(Builder $query) => $query->where("year_period_id", $currentYearPeriod->id)->whereNotNull("days"),
)
->where("id", "!=", $currentUser->id)
->orderBy("last_name")
->orderBy("first_name")
->get();
if ($currentUser->hasVacationLimit($currentYearPeriod)) {
$users->prepend($currentUser);
}
$monthlyUsage = [];
foreach ($users as $user) {
$vacationsByMonth = $statsRetriever->getUsedVacationDaysByMonth($user, $currentYearPeriod);
$limit = $statsRetriever->getVacationDaysLimit($user, $currentYearPeriod);
$used = $statsRetriever->getUsedVacationDays($user, $currentYearPeriod);
$pending = $statsRetriever->getPendingVacationDays($user, $currentYearPeriod);
$remaining = $limit - $used - $pending;
$monthlyUsage[] = [
"user" => new UserResource($user),
"months" => $vacationsByMonth,
"stats" => [
"used" => $used,
"pending" => $pending,
"remaining" => $remaining,
],
];
}
return inertia("MonthlyUsage", [
"monthlyUsage" => $monthlyUsage,
"currentMonth" => Month::current(),
"can" => [
"listMonthlyUsage" => $request->user()->can("listMonthlyUsage"),
],
]);
}
}

View File

@ -17,6 +17,6 @@ class SelectYearPeriodController extends Controller
return redirect()
->back()
->with("success", __("Selected year period has been changed."));
->with("info", __("Selected year period has been changed."));
}
}

View File

@ -27,11 +27,13 @@ class HandleInertiaRequests extends Middleware
"manageVacationLimits" => $user ? $user->can("manageVacationLimits") : false,
"manageUsers" => $user ? $user->can("manageUsers") : false,
"listAllVacationRequests" => $user ? $user->can("listAll", VacationRequest::class) : false,
"listMonthlyUsage" => $user ? $user->can("listMonthlyUsage") : false,
],
],
"flash" => fn() => [
"success" => $request->session()->get("success"),
"error" => $request->session()->get("error"),
"info" => $request->session()->get("info"),
],
"years" => fn() => $user ? $this->yearPeriodRetriever->links() : [],
]);

869
composer.lock generated

File diff suppressed because it is too large Load Diff

296
package-lock.json generated
View File

@ -4,24 +4,25 @@
"requires": true,
"packages": {
"": {
"hasInstallScript": true,
"dependencies": {
"@headlessui/vue": "^1.5.0",
"@heroicons/vue": "^1.0.5",
"@heroicons/vue": "^1.0.6",
"@inertiajs/inertia": "^0.11.0",
"@inertiajs/inertia-vue3": "^0.6.0",
"@inertiajs/progress": "^0.2.7",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/forms": "^0.5.0",
"@tailwindcss/line-clamp": "^0.3.1",
"@tailwindcss/typography": "^0.5.2",
"@vue/compiler-sfc": "^3.2.31",
"autoprefixer": "^10.4.2",
"axios": "^0.26.0",
"echarts": "^5.3.0",
"autoprefixer": "^10.4.4",
"axios": "^0.26.1",
"echarts": "^5.3.1",
"eslit": "^6.0.0",
"flatpickr": "^4.6.9",
"flatpickr": "^4.6.11",
"laravel-mix": "^6.0.43",
"lodash": "^4.17.21",
"postcss": "^8.4.7",
"postcss": "^8.4.12",
"tailwindcss": "^3.0.23",
"vue": "^3.2.31",
"vue-echarts": "^6.0.2",
@ -32,7 +33,7 @@
"vue3-popper": "^1.4.2"
},
"devDependencies": {
"eslint": "^8.10.0",
"eslint": "^8.11.0",
"eslint-plugin-vue": "^8.5.0"
}
},
@ -1659,16 +1660,16 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz",
"integrity": "sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.3.1",
"globals": "^13.9.0",
"ignore": "^4.0.6",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.0.4",
@ -1678,15 +1679,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@eslint/eslintrc/node_modules/ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/@headlessui/vue": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.5.0.tgz",
@ -1699,9 +1691,9 @@
}
},
"node_modules/@heroicons/vue": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-1.0.5.tgz",
"integrity": "sha512-idWtp20Fjr7mqnD7EdGDUDi83oWHnx3SwyuQY6GZyF33OApzpBOLxz7xa4t6rPOddGz9tI5RGnndLk+ake7ijQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-1.0.6.tgz",
"integrity": "sha512-ng2YcCQrdoQWEFpw+ipFl2rZo8mZ56v0T5+MyfQQvNqfKChwgP6DMloZLW+rl17GEcHkE3H82UTAMKBKZr4+WA==",
"peerDependencies": {
"vue": ">= 3"
}
@ -1832,9 +1824,9 @@
}
},
"node_modules/@tailwindcss/forms": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.4.0.tgz",
"integrity": "sha512-DeaQBx6EgEeuZPQACvC+mKneJsD8am1uiJugjgQK1+/Vt+Ai0GpFBC2T2fqnUad71WgOxyrZPE6BG1VaI6YqfQ==",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.0.tgz",
"integrity": "sha512-KzWugryEBFkmoaYcBE18rs6gthWCFHHO7cAZm2/hv3hwD67AzwP7udSCa22E7R1+CEJL/FfhYsJWrc0b1aeSzw==",
"dependencies": {
"mini-svg-data-uri": "^1.2.3"
},
@ -2663,13 +2655,23 @@
}
},
"node_modules/autoprefixer": {
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz",
"integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==",
"version": "10.4.4",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.4.tgz",
"integrity": "sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
}
],
"dependencies": {
"browserslist": "^4.19.1",
"caniuse-lite": "^1.0.30001297",
"fraction.js": "^4.1.2",
"browserslist": "^4.20.2",
"caniuse-lite": "^1.0.30001317",
"fraction.js": "^4.2.0",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
"postcss-value-parser": "^4.2.0"
@ -2680,18 +2682,14 @@
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/axios": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
"integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"dependencies": {
"follow-redirects": "^1.14.8"
}
@ -3011,12 +3009,22 @@
}
},
"node_modules/browserslist": {
"version": "4.19.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz",
"integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==",
"version": "4.20.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz",
"integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
}
],
"dependencies": {
"caniuse-lite": "^1.0.30001312",
"electron-to-chromium": "^1.4.71",
"caniuse-lite": "^1.0.30001317",
"electron-to-chromium": "^1.4.84",
"escalade": "^3.1.1",
"node-releases": "^2.0.2",
"picocolors": "^1.0.0"
@ -3026,10 +3034,6 @@
},
"engines": {
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
}
},
"node_modules/buffer": {
@ -3119,13 +3123,19 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001312",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz",
"integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==",
"funding": {
"version": "1.0.30001320",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz",
"integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
}
]
},
"node_modules/chalk": {
"version": "4.1.2",
@ -4155,12 +4165,12 @@
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/echarts": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.0.tgz",
"integrity": "sha512-zENufmwFE6WjM+24tW3xQq4ICqQtI0CGj4bDVDNd3BK3LtaA/5wBp+64ykIyKy3QElz0cieKqSYP4FX9Lv9MwQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.1.tgz",
"integrity": "sha512-nWdlbgX3OVY0hpqncSvp0gDt1FRSKWn7lsWEH+PHmfCuvE0QmSw17pczQvm8AvawnLEkmf1Cts7YwQJZNC0AEQ==",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.3.0"
"zrender": "5.3.1"
}
},
"node_modules/ee-first": {
@ -4169,9 +4179,9 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"node_modules/electron-to-chromium": {
"version": "1.4.75",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz",
"integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q=="
"version": "1.4.92",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.92.tgz",
"integrity": "sha512-YAVbvQIcDE/IJ/vzDMjD484/hsRbFPW2qXJPaYTfOhtligmfYEYOep+5QojpaEU9kq6bMvNeC2aG7arYvTHYsA=="
},
"node_modules/elliptic": {
"version": "6.5.4",
@ -4283,12 +4293,12 @@
}
},
"node_modules/eslint": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz",
"integrity": "sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz",
"integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.2.0",
"@eslint/eslintrc": "^1.2.1",
"@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
@ -4830,9 +4840,9 @@
}
},
"node_modules/flatpickr": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz",
"integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw=="
"version": "4.6.11",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.11.tgz",
"integrity": "sha512-/rnbE/hu5I5zndLEyYfYvqE4vPDvI5At0lFcQA5eOPfjquZLcQ0HMKTL7rv5/+DvbPM3/vJcXpXjB/DjBh+1jw=="
},
"node_modules/flatted": {
"version": "3.2.5",
@ -4868,9 +4878,9 @@
}
},
"node_modules/fraction.js": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.3.tgz",
"integrity": "sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
"engines": {
"node": "*"
},
@ -5010,9 +5020,9 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
},
"node_modules/globals": {
"version": "13.12.1",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz",
"integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==",
"version": "13.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
"integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@ -6923,9 +6933,19 @@
}
},
"node_modules/postcss": {
"version": "8.4.7",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz",
"integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==",
"version": "8.4.12",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz",
"integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.3.1",
"picocolors": "^1.0.0",
@ -6933,10 +6953,6 @@
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/postcss-calc": {
@ -9575,9 +9591,9 @@
}
},
"node_modules/zrender": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.3.0.tgz",
"integrity": "sha512-Ln2QB5uqI1ftNYMtCRxd+XDq6MOttLgam2tmhKAVA+j0ko47UT+VNlDvKTkqe4K2sJhBvB0EhYNLebqlCTjatQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.3.1.tgz",
"integrity": "sha512-7olqIjy0gWfznKr6vgfnGBk7y4UtdMvdwFmK92vVQsQeDPyzkHW1OlrLEKg6GHz1W5ePf0FeN1q2vkl/HFqhXw==",
"dependencies": {
"tslib": "2.3.0"
}
@ -10699,28 +10715,20 @@
"integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA=="
},
"@eslint/eslintrc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz",
"integrity": "sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.3.1",
"globals": "^13.9.0",
"ignore": "^4.0.6",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.0.4",
"strip-json-comments": "^3.1.1"
},
"dependencies": {
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
}
}
},
"@headlessui/vue": {
@ -10730,9 +10738,9 @@
"requires": {}
},
"@heroicons/vue": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-1.0.5.tgz",
"integrity": "sha512-idWtp20Fjr7mqnD7EdGDUDi83oWHnx3SwyuQY6GZyF33OApzpBOLxz7xa4t6rPOddGz9tI5RGnndLk+ake7ijQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-1.0.6.tgz",
"integrity": "sha512-ng2YcCQrdoQWEFpw+ipFl2rZo8mZ56v0T5+MyfQQvNqfKChwgP6DMloZLW+rl17GEcHkE3H82UTAMKBKZr4+WA==",
"requires": {}
},
"@humanwhocodes/config-array": {
@ -10837,9 +10845,9 @@
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
},
"@tailwindcss/forms": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.4.0.tgz",
"integrity": "sha512-DeaQBx6EgEeuZPQACvC+mKneJsD8am1uiJugjgQK1+/Vt+Ai0GpFBC2T2fqnUad71WgOxyrZPE6BG1VaI6YqfQ==",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.0.tgz",
"integrity": "sha512-KzWugryEBFkmoaYcBE18rs6gthWCFHHO7cAZm2/hv3hwD67AzwP7udSCa22E7R1+CEJL/FfhYsJWrc0b1aeSzw==",
"requires": {
"mini-svg-data-uri": "^1.2.3"
}
@ -11585,22 +11593,22 @@
}
},
"autoprefixer": {
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz",
"integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==",
"version": "10.4.4",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.4.tgz",
"integrity": "sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA==",
"requires": {
"browserslist": "^4.19.1",
"caniuse-lite": "^1.0.30001297",
"fraction.js": "^4.1.2",
"browserslist": "^4.20.2",
"caniuse-lite": "^1.0.30001317",
"fraction.js": "^4.2.0",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
"postcss-value-parser": "^4.2.0"
}
},
"axios": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
"integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"requires": {
"follow-redirects": "^1.14.8"
}
@ -11855,12 +11863,12 @@
}
},
"browserslist": {
"version": "4.19.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz",
"integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==",
"version": "4.20.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz",
"integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==",
"requires": {
"caniuse-lite": "^1.0.30001312",
"electron-to-chromium": "^1.4.71",
"caniuse-lite": "^1.0.30001317",
"electron-to-chromium": "^1.4.84",
"escalade": "^3.1.1",
"node-releases": "^2.0.2",
"picocolors": "^1.0.0"
@ -11941,9 +11949,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001312",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz",
"integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ=="
"version": "1.0.30001320",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz",
"integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA=="
},
"chalk": {
"version": "4.1.2",
@ -12730,12 +12738,12 @@
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"echarts": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.0.tgz",
"integrity": "sha512-zENufmwFE6WjM+24tW3xQq4ICqQtI0CGj4bDVDNd3BK3LtaA/5wBp+64ykIyKy3QElz0cieKqSYP4FX9Lv9MwQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.1.tgz",
"integrity": "sha512-nWdlbgX3OVY0hpqncSvp0gDt1FRSKWn7lsWEH+PHmfCuvE0QmSw17pczQvm8AvawnLEkmf1Cts7YwQJZNC0AEQ==",
"requires": {
"tslib": "2.3.0",
"zrender": "5.3.0"
"zrender": "5.3.1"
}
},
"ee-first": {
@ -12744,9 +12752,9 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"electron-to-chromium": {
"version": "1.4.75",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz",
"integrity": "sha512-LxgUNeu3BVU7sXaKjUDD9xivocQLxFtq6wgERrutdY/yIOps3ODOZExK1jg8DTEg4U8TUCb5MLGeWFOYuxjF3Q=="
"version": "1.4.92",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.92.tgz",
"integrity": "sha512-YAVbvQIcDE/IJ/vzDMjD484/hsRbFPW2qXJPaYTfOhtligmfYEYOep+5QojpaEU9kq6bMvNeC2aG7arYvTHYsA=="
},
"elliptic": {
"version": "6.5.4",
@ -12833,12 +12841,12 @@
"dev": true
},
"eslint": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz",
"integrity": "sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz",
"integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.2.0",
"@eslint/eslintrc": "^1.2.1",
"@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
@ -13251,9 +13259,9 @@
}
},
"flatpickr": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz",
"integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw=="
"version": "4.6.11",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.11.tgz",
"integrity": "sha512-/rnbE/hu5I5zndLEyYfYvqE4vPDvI5At0lFcQA5eOPfjquZLcQ0HMKTL7rv5/+DvbPM3/vJcXpXjB/DjBh+1jw=="
},
"flatted": {
"version": "3.2.5",
@ -13272,9 +13280,9 @@
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
},
"fraction.js": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.3.tgz",
"integrity": "sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg=="
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA=="
},
"fresh": {
"version": "0.5.2",
@ -13370,9 +13378,9 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
},
"globals": {
"version": "13.12.1",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz",
"integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==",
"version": "13.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
"integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
@ -14774,9 +14782,9 @@
}
},
"postcss": {
"version": "8.4.7",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz",
"integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==",
"version": "8.4.12",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz",
"integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==",
"requires": {
"nanoid": "^3.3.1",
"picocolors": "^1.0.0",
@ -16615,9 +16623,9 @@
"integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg=="
},
"zrender": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.3.0.tgz",
"integrity": "sha512-Ln2QB5uqI1ftNYMtCRxd+XDq6MOttLgam2tmhKAVA+j0ko47UT+VNlDvKTkqe4K2sJhBvB0EhYNLebqlCTjatQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.3.1.tgz",
"integrity": "sha512-7olqIjy0gWfznKr6vgfnGBk7y4UtdMvdwFmK92vVQsQeDPyzkHW1OlrLEKg6GHz1W5ePf0FeN1q2vkl/HFqhXw==",
"requires": {
"tslib": "2.3.0"
}

View File

@ -14,22 +14,22 @@
},
"dependencies": {
"@headlessui/vue": "^1.5.0",
"@heroicons/vue": "^1.0.5",
"@heroicons/vue": "^1.0.6",
"@inertiajs/inertia": "^0.11.0",
"@inertiajs/inertia-vue3": "^0.6.0",
"@inertiajs/progress": "^0.2.7",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/forms": "^0.5.0",
"@tailwindcss/line-clamp": "^0.3.1",
"@tailwindcss/typography": "^0.5.2",
"@vue/compiler-sfc": "^3.2.31",
"autoprefixer": "^10.4.2",
"axios": "^0.26.0",
"echarts": "^5.3.0",
"autoprefixer": "^10.4.4",
"axios": "^0.26.1",
"echarts": "^5.3.1",
"eslit": "^6.0.0",
"flatpickr": "^4.6.9",
"flatpickr": "^4.6.11",
"laravel-mix": "^6.0.43",
"lodash": "^4.17.21",
"postcss": "^8.4.7",
"postcss": "^8.4.12",
"tailwindcss": "^3.0.23",
"vue": "^3.2.31",
"vue-echarts": "^6.0.2",
@ -40,7 +40,7 @@
"vue3-popper": "^1.4.2"
},
"devDependencies": {
"eslint": "^8.10.0",
"eslint": "^8.11.0",
"eslint-plugin-vue": "^8.5.0"
}
}

View File

@ -2,50 +2,62 @@ const months = [
{
'name': 'Styczeń',
'value': 'january',
'shortcut': 'Sty',
},
{
'name': 'Luty',
'value': 'february',
'shortcut': 'Lut',
},
{
'name': 'Marzec',
'value': 'march',
'shortcut': 'Mar',
},
{
'name': 'Kwiecień',
'value': 'april',
'shortcut': 'Kwi',
},
{
'name': 'Maj',
'value': 'may',
'shortcut':'Maj',
},
{
'name': 'Czerwiec',
'value': 'june',
'shortcut': 'Cze',
},
{
'name': 'Lipiec',
'value': 'july',
'shortcut': 'Lip',
},
{
'name': 'Sierpień',
'value': 'august',
'shortcut': 'Sie',
},
{
'name': 'Wrzesień',
'value': 'september',
'shortcut': 'Wrz',
},
{
'name': 'Październik',
'value': 'october',
'shortcut': 'Paź',
},
{
'name': 'Listopad',
'value': 'november',
'shortcut': 'Lis',
},
{
'name': 'Grudzień',
'value': 'december',
'shortcut': 'Gru',
},
]

View File

@ -22,7 +22,7 @@
<div class="border-t border-gray-200">
<div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-100">
<thead class="bg-gray-50">
<tr>
<th
scope="col"

View File

@ -0,0 +1,94 @@
<template>
<InertiaHead title="Wykorzystanie miesięczne urlopu" />
<div class="bg-white shadow-md">
<div class="flex justify-between items-center p-4 sm:px-6">
<div class="flex items-center">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Wykorzystanie miesięczne urlopu wypoczynkowego
</h2>
</div>
</div>
<div class="border-t border-gray-200">
<div class="overflow-x-auto xl:overflow-x-visible overflow-y-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="w-64 px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider text-left">
Pracownik
</th>
<th
v-for="month in months"
:key="month"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider text-center"
:class="{'bg-blumilk-50': isCurrentMonth(month)}"
style="min-width: 46px;"
>
<span :class="{'text-blumilk-600': isCurrentMonth(month)}">
{{ month.shortcut }}
</span>
</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider text-center">
Wykorzystanie urlopu
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
<tr
v-for="item in monthlyUsage"
:key="item.user.id"
class="hover:bg-blumilk-25"
>
<th class="px-4 py-4 whitespace-nowrap text-sm text-gray-500 font-semibold capitalize">
<div class="flex justify-start items-center">
<span class="inline-flex items-center justify-center h-10 w-10 rounded-full">
<img
class="h-10 w-10 rounded-full"
:src="item.user.avatar"
>
</span>
<div class="ml-3">
<div
class="text-sm font-medium text-gray-900 whitespace-nowrap"
>
{{ item.user.name }}
</div>
</div>
</div>
</th>
<td
v-for="month in months"
:key="month.value"
class="px-4 py-4 text-sm text-gray-500 font-semibold text-center"
:class="{'bg-blumilk-25': isCurrentMonth(month)}"
>
{{ item.months[month.value] ?? '-' }}
</td>
<td class="px-4 py-4 text-sm text-gray-500 font-semibold text-center">
<div style="min-width: 300px;">
<VacationBar :stats="{ used: item.stats.used, pending: item.stats.pending, remaining: item.stats.remaining }" />
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup>
import { useMonthInfo } from '@/Composables/monthInfo'
import VacationBar from '@/Shared/VacationBar'
const props = defineProps({
monthlyUsage: Object,
currentMonth: String,
})
const { getMonths } = useMonthInfo()
const months = getMonths()
function isCurrentMonth(month) {
return props.currentMonth === month.value
}
</script>

View File

@ -11,6 +11,7 @@
</p>
</div>
</div>
<div class="border-t border-gray-200">
<div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible">
<form @submit.prevent="submitVacationDays">
<table class="min-w-full divide-y divide-gray-200">
@ -130,6 +131,7 @@
</form>
</div>
</div>
</div>
</template>
<script setup>

View File

@ -15,7 +15,7 @@
<script setup>
import MainMenu from '@/Shared/MainMenu'
import { useToast } from 'vue-toastification'
import { defineProps, watch } from 'vue'
import { watch } from 'vue'
const props = defineProps({
flash: Object,
@ -30,6 +30,10 @@ watch(() => props.flash, flash => {
toast.success(flash.success)
}
if (flash.info) {
toast.info(flash.info)
}
if (flash.error) {
toast.error(flash.error)
}

View File

@ -268,7 +268,9 @@ import {
XIcon,
SunIcon,
StarIcon,
CalendarIcon, DocumentTextIcon,
CalendarIcon,
DocumentTextIcon,
AdjustmentsIcon,
} from '@heroicons/vue/outline'
import { CheckIcon, ChevronDownIcon } from '@heroicons/vue/solid'
@ -302,6 +304,13 @@ const navigation = computed(() =>
icon: CalendarIcon,
can: true,
},
{
name: 'Wykorzystanie urlopu',
href: '/monthly-usage',
component: 'MonthlyUsage',
icon: AdjustmentsIcon,
can: props.auth.can.listMonthlyUsage,
},
{
name: 'Dni wolne',
href: '/holidays',

View File

@ -0,0 +1,84 @@
<template>
<Popper
hover
class="h-full w-full"
>
<div class="flex bg-white text-white">
<div
v-show="stats.used > 0"
:style="`background-color: ${colors.used}; flex-basis: ${calculatePercent(stats.used)}%;`"
class="flex items-center justify-center py-2 px-0.5"
>
<strong>{{ stats.used }}</strong>
</div>
<div
v-show="stats.pending > 0"
:style="`background-color: ${colors.pending}; flex-basis: ${calculatePercent(stats.pending)}%;`"
class="flex items-center justify-center py-2 px-0.5"
>
<strong>{{ stats.pending }}</strong>
</div>
<div
v-show="stats.remaining > 0"
:style="`background-color: ${colors.remaining}; flex-basis: ${calculatePercent(stats.remaining)}%;`"
class="flex items-center justify-center py-2 px-0.5"
>
<strong>{{ stats.remaining }}</strong>
</div>
</div>
<template #content>
<div class="px-4 py-2 bg-white text-md text-gray-900 rounded-md shadow-md flext">
<div class="flex items-center font-normal">
<i
class="inline-block w-5 h-3 mr-3"
:style="`background-color: ${colors.used}`"
/>
Wykorzystane:
<span class="font-semibold ml-1">{{ stats.used }}</span>
</div>
<div class="flex items-center font-normal">
<i
class="inline-block w-5 h-3 mr-3"
:style="`background-color: ${colors.pending}`"
/>
Rozpatrywane:
<span class="font-semibold ml-1">{{ stats.pending }}</span>
</div>
<div class="flex items-center font-normal">
<i
class="inline-block w-5 h-3 mr-3"
:style="`background-color: ${colors.remaining}`"
/>
Pozostałe:
<span class="font-semibold ml-1">{{ stats.remaining }}</span>
</div>
</div>
</template>
</Popper>
</template>
<script setup>
import Popper from 'vue3-popper'
const props = defineProps({
stats: {
type: Object,
default: () => ({
used: 0,
pending: 0,
remaining: 0,
}),
},
})
const colors = {
used: '#2C466F',
pending: '#AABDDD',
remaining: '#527ABA',
}
function calculatePercent(value) {
return value / (props.stats.used + props.stats.pending + props.stats.remaining) * 100
}
</script>

View File

@ -20,7 +20,8 @@ createInertiaApp({
.use(Toast, {
position: 'bottom-right',
maxToast: 5,
timeout: 3000,
pauseOnFocusLoss: false,
})
.component('InertiaLink', Link)
.component('InertiaHead', Head)

View File

@ -7,6 +7,7 @@ use Toby\Infrastructure\Http\Controllers\DashboardController;
use Toby\Infrastructure\Http\Controllers\GoogleController;
use Toby\Infrastructure\Http\Controllers\HolidayController;
use Toby\Infrastructure\Http\Controllers\LogoutController;
use Toby\Infrastructure\Http\Controllers\MonthlyUsageController;
use Toby\Infrastructure\Http\Controllers\SelectYearPeriodController;
use Toby\Infrastructure\Http\Controllers\TimesheetController;
use Toby\Infrastructure\Http\Controllers\UserController;
@ -63,6 +64,8 @@ Route::middleware("auth")->group(function (): void {
Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class)
->name("year-periods.select");
Route::get("/monthly-usage", MonthlyUsageController::class)->name("monthly-usage");
});
Route::middleware("guest")->group(function (): void {