fix
This commit is contained in:
parent
45510a898b
commit
d2babab7a0
@ -35,6 +35,6 @@ class AuthServiceProvider extends ServiceProvider
|
||||
Gate::define("manageVacationLimits", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
|
||||
Gate::define("generateTimesheet", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
|
||||
Gate::define("listMonthlyUsage", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
|
||||
Gate::define("manageResumes", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
|
||||
Gate::define("manageResumes", fn(User $user): bool => $user->role === Role::TechnicalApprover);
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class ResumeGenerator
|
||||
|
||||
protected function fillTechnologies(TemplateProcessor $processor, Resume $resume): void
|
||||
{
|
||||
if ($resume->technologies->count() <= 0) {
|
||||
if ($resume->technologies->isEmpty()) {
|
||||
$processor->deleteBlock("technologies");
|
||||
|
||||
return;
|
||||
@ -51,7 +51,7 @@ class ResumeGenerator
|
||||
|
||||
protected function fillLanguages(TemplateProcessor $processor, Resume $resume): void
|
||||
{
|
||||
if ($resume->education->count() <= 0) {
|
||||
if ($resume->education->isEmpty()) {
|
||||
$processor->deleteBlock("languages");
|
||||
|
||||
return;
|
||||
@ -62,7 +62,7 @@ class ResumeGenerator
|
||||
|
||||
protected function fillEducation(TemplateProcessor $processor, Resume $resume): void
|
||||
{
|
||||
if ($resume->education->count() <= 0) {
|
||||
if ($resume->education->isEmpty()) {
|
||||
$processor->deleteBlock("education");
|
||||
|
||||
return;
|
||||
@ -73,7 +73,7 @@ class ResumeGenerator
|
||||
|
||||
protected function fillProjects(TemplateProcessor $processor, Resume $resume): void
|
||||
{
|
||||
if ($resume->projects->count() <= 0) {
|
||||
if ($resume->projects->isEmpty()) {
|
||||
$processor->deleteBlock("projects");
|
||||
|
||||
return;
|
||||
@ -94,7 +94,7 @@ class ResumeGenerator
|
||||
return [
|
||||
"index#{$index}" => $index,
|
||||
"start_date#{$index}" => Carbon::create($project["startDate"])->toDisplayString(),
|
||||
"end_date#{$index}" => Carbon::create($project["endDate"])->toDisplayString(),
|
||||
"end_date#{$index}" => $project["current"] ? "present" : Carbon::create($project["endDate"])->format("m.Y"),
|
||||
"description#{$index}" => $project["description"],
|
||||
"tasks#{$index}" => $this->withNewLines($project["tasks"]),
|
||||
];
|
||||
@ -134,7 +134,7 @@ class ResumeGenerator
|
||||
{
|
||||
return $resume->education->map(fn(array $project, int $index): array => [
|
||||
"start_date" => Carbon::create($project["startDate"])->toDisplayString(),
|
||||
"end_date" => Carbon::create($project["endDate"])->toDisplayString(),
|
||||
"end_date" => $project["current"] ? "present" : Carbon::create($project["endDate"])->format("m.Y"),
|
||||
"school" => $project["school"],
|
||||
"field_of_study" => $project["fieldOfStudy"],
|
||||
"degree" => $project["degree"],
|
||||
|
@ -16,6 +16,8 @@ class Technology extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function newFactory(): TechnologyFactory
|
||||
{
|
||||
return TechnologyFactory::new();
|
||||
|
57
app/Infrastructure/Http/Controllers/TechnologyController.php
Normal file
57
app/Infrastructure/Http/Controllers/TechnologyController.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Infrastructure\Http\Controllers;
|
||||
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Inertia\Response;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Toby\Eloquent\Models\Technology;
|
||||
use Toby\Infrastructure\Http\Requests\TechnologyRequest;
|
||||
use Toby\Infrastructure\Http\Resources\TechnologyResource;
|
||||
|
||||
class TechnologyController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
{
|
||||
$this->authorize("manageResumes");
|
||||
|
||||
$technologies = Technology::query()
|
||||
->orderBy("name")
|
||||
->get();
|
||||
|
||||
return inertia("Technologies", [
|
||||
"technologies" => TechnologyResource::collection($technologies),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function store(TechnologyRequest $request): RedirectResponse
|
||||
{
|
||||
$this->authorize("manageResumes");
|
||||
|
||||
$technology = Technology::query()->create($request->data());
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with("success", __("Technology :name has been created.", [
|
||||
"name" => $technology->name,
|
||||
]));
|
||||
}
|
||||
|
||||
public function destroy(Technology $technology): RedirectResponse
|
||||
{
|
||||
$this->authorize("manageResumes");
|
||||
|
||||
$technology->delete();
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with("success", __("Technology :name has been deleted.", [
|
||||
"name" => $technology->name,
|
||||
]));
|
||||
}
|
||||
}
|
@ -20,19 +20,21 @@ class ResumeRequest extends FormRequest
|
||||
"education.*.school" => ["required"],
|
||||
"education.*.degree" => ["required"],
|
||||
"education.*.fieldOfStudy" => ["required"],
|
||||
"education.*.startDate" => ["required", "date_format:Y-m-d"],
|
||||
"education.*.endDate" => ["required", "date_format:Y-m-d", "after:education.*.startDate"],
|
||||
"education.*.startDate" => ["required", "date_format:m/Y"],
|
||||
"education.*.current" => ["required", "boolean"],
|
||||
"education.*.endDate" => ["required_if:education.*.current,false", "nullable", "date_format:m/Y", "after:education.*.startDate"],
|
||||
|
||||
"languages.*.name" => ["required", "distinct"],
|
||||
"languages.*.level" => ["required", Rule::in(1, 2, 3, 4, 5, 6)],
|
||||
|
||||
"technologies.*.name" => ["required", "exists:technologies,name", "distinct"],
|
||||
"technologies.*.name" => ["required", "distinct"],
|
||||
"technologies.*.level" => ["required", Rule::in(1, 2, 3, 4, 5)],
|
||||
|
||||
"projects.*.description" => ["required"],
|
||||
"projects.*.technologies" => ["array", "min:1", "exists:technologies,name", "distinct"],
|
||||
"projects.*.startDate" => ["required", "date_format:Y-m-d"],
|
||||
"projects.*.endDate" => ["required", "date_format:Y-m-d", "after:projects.*.startDate"],
|
||||
"projects.*.technologies" => ["array", "min:1", "distinct"],
|
||||
"projects.*.startDate" => ["required", "date_format:m/Y"],
|
||||
"projects.*.current" => ["required", "boolean"],
|
||||
"projects.*.endDate" => ["required_if:projects.*.current,false", "nullable", "date_format:m/Y", "after:projects.*.startDate"],
|
||||
"projects.*.tasks" => ["required"],
|
||||
];
|
||||
}
|
||||
|
29
app/Infrastructure/Http/Requests/TechnologyRequest.php
Normal file
29
app/Infrastructure/Http/Requests/TechnologyRequest.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Infrastructure\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class TechnologyRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
"name" => [
|
||||
"required",
|
||||
Rule::unique("technologies", "name")->ignore($this->technology),
|
||||
"max:255",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
"name" => $this->get("name"),
|
||||
];
|
||||
}
|
||||
}
|
20
app/Infrastructure/Http/Resources/TechnologyResource.php
Normal file
20
app/Infrastructure/Http/Resources/TechnologyResource.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Infrastructure\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TechnologyResource extends JsonResource
|
||||
{
|
||||
public static $wrap = null;
|
||||
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
"id" => $this->id,
|
||||
"name" => $this->name,
|
||||
];
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Toby\Eloquent\Models\Resume;
|
||||
use Toby\Eloquent\Models\Technology;
|
||||
@ -33,8 +34,9 @@ class ResumeFactory extends Factory
|
||||
"school" => $this->faker->sentence,
|
||||
"degree" => $this->faker->sentence,
|
||||
"fieldOfStudy" => $this->faker->sentence,
|
||||
"startDate" => $this->faker->date,
|
||||
"endDate" => $this->faker->date,
|
||||
"current" => false,
|
||||
"startDate" => Carbon::create($this->faker->date)->format("m/Y"),
|
||||
"endDate" => Carbon::create($this->faker->date)->format("m/Y"),
|
||||
];
|
||||
}
|
||||
|
||||
@ -78,8 +80,9 @@ class ResumeFactory extends Factory
|
||||
$items[] = [
|
||||
"description" => $this->faker->text,
|
||||
"technologies" => $technologies->random($number)->all(),
|
||||
"startDate" => $this->faker->date,
|
||||
"endDate" => $this->faker->date,
|
||||
"current" => false,
|
||||
"startDate" => Carbon::create($this->faker->date)->format("m/Y"),
|
||||
"endDate" => Carbon::create($this->faker->date)->format("m/Y"),
|
||||
"tasks" => $this->faker->text,
|
||||
];
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
@import 'flatpickr/dist/themes/light.css';
|
||||
@import 'flatpickr/dist/plugins/monthSelect/style.css';
|
||||
@import 'vue-toastification/dist/index.css';
|
||||
|
||||
@tailwind base;
|
||||
|
@ -207,7 +207,7 @@
|
||||
Data rozpoczęcia
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0">
|
||||
<FlatPickr
|
||||
<MonthPicker
|
||||
v-model="element.startDate"
|
||||
placeholder="Wybierz datę"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm"
|
||||
@ -226,12 +226,23 @@
|
||||
Data zakończenia
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0">
|
||||
<FlatPickr
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700 sm:mt-px">
|
||||
<input
|
||||
v-model="element.current"
|
||||
type="checkbox"
|
||||
class="focus:ring-blumilk-500 h-4 w-4 text-blumilk-600 border-gray-300 rounded mr-1"
|
||||
>
|
||||
W trakcie
|
||||
</label>
|
||||
<MonthPicker
|
||||
v-model="element.endDate"
|
||||
placeholder="Wybierz datę"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm"
|
||||
:disabled="element.current"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm disabled:bg-gray-100"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`education.${index}.endDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`education.${index}.endDate`] }"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
v-if="form.errors[`education.${index}.endDate`]"
|
||||
class="mt-2 text-sm text-red-600"
|
||||
@ -436,12 +447,12 @@
|
||||
Data rozpoczęcia
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0">
|
||||
<FlatPickr
|
||||
<MonthPicker
|
||||
:id="`project-startDate-${index}`"
|
||||
v-model="element.startDate"
|
||||
placeholder="Wybierz datę"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`educations.${index}.startDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`educations.${index}.startDate`] }"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`projects.${index}.startDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.startDate`] }"
|
||||
/>
|
||||
<p
|
||||
v-if="form.errors[`projects.${index}.startDate`]"
|
||||
@ -459,13 +470,24 @@
|
||||
Data zakończenia
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0">
|
||||
<FlatPickr
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700 sm:mt-px">
|
||||
<input
|
||||
v-model="element.current"
|
||||
type="checkbox"
|
||||
class="focus:ring-blumilk-500 h-4 w-4 text-blumilk-600 border-gray-300 rounded mr-1"
|
||||
>
|
||||
W trakcie
|
||||
</label>
|
||||
<MonthPicker
|
||||
:id="`project-endDate-${index}`"
|
||||
v-model="element.endDate"
|
||||
placeholder="Wybierz datę"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`educations.${index}.endDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`educations.${index}.endDate`] }"
|
||||
:disabled="element.current"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm disabled:bg-gray-100"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`projects.${index}.endDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.endDate`] }"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
v-if="form.errors[`projects.${index}.endDate`]"
|
||||
class="mt-2 text-sm text-red-600"
|
||||
@ -525,7 +547,7 @@ import { Listbox, ListboxOption, ListboxOptions, ListboxLabel, ListboxButton } f
|
||||
import { SelectorIcon, CheckIcon } from '@heroicons/vue/outline'
|
||||
import { ExclamationCircleIcon } from '@heroicons/vue/solid'
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
import FlatPickr from 'vue-flatpickr-component'
|
||||
import MonthPicker from '@/Shared/Forms/MonthPicker'
|
||||
import DynamicSection from '@/Shared/Forms/DynamicSection'
|
||||
import Combobox from '@/Shared/Forms/Combobox'
|
||||
import MultipleCombobox from '@/Shared/Forms/MultipleCombobox'
|
||||
|
@ -207,7 +207,7 @@
|
||||
Data rozpoczęcia
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0">
|
||||
<FlatPickr
|
||||
<MonthPicker
|
||||
v-model="element.startDate"
|
||||
placeholder="Wybierz datę"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm"
|
||||
@ -226,12 +226,23 @@
|
||||
Data zakończenia
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0">
|
||||
<FlatPickr
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700 sm:mt-px">
|
||||
<input
|
||||
v-model="element.current"
|
||||
type="checkbox"
|
||||
class="focus:ring-blumilk-500 h-4 w-4 text-blumilk-600 border-gray-300 rounded mr-1"
|
||||
>
|
||||
W trakcie
|
||||
</label>
|
||||
<MonthPicker
|
||||
v-model="element.endDate"
|
||||
placeholder="Wybierz datę"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm"
|
||||
:disabled="element.current"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm disabled:bg-gray-100"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`education.${index}.endDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`education.${index}.endDate`] }"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
v-if="form.errors[`education.${index}.endDate`]"
|
||||
class="mt-2 text-sm text-red-600"
|
||||
@ -436,12 +447,12 @@
|
||||
Data rozpoczęcia
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0">
|
||||
<FlatPickr
|
||||
<MonthPicker
|
||||
:id="`project-startDate-${index}`"
|
||||
v-model="element.startDate"
|
||||
placeholder="Wybierz datę"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`educations.${index}.startDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`educations.${index}.startDate`] }"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`projects.${index}.startDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.startDate`] }"
|
||||
/>
|
||||
<p
|
||||
v-if="form.errors[`projects.${index}.startDate`]"
|
||||
@ -459,13 +470,24 @@
|
||||
Data zakończenia
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0">
|
||||
<FlatPickr
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700 sm:mt-px">
|
||||
<input
|
||||
v-model="element.current"
|
||||
type="checkbox"
|
||||
class="focus:ring-blumilk-500 h-4 w-4 text-blumilk-600 border-gray-300 rounded mr-1"
|
||||
>
|
||||
W trakcie
|
||||
</label>
|
||||
<MonthPicker
|
||||
:id="`project-endDate-${index}`"
|
||||
v-model="element.endDate"
|
||||
placeholder="Wybierz datę"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`educations.${index}.endDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`educations.${index}.endDate`] }"
|
||||
:disabled="element.current"
|
||||
class="block w-full rounded-md shadow-sm sm:text-sm disabled:bg-gray-100"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`projects.${index}.endDate`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.endDate`] }"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
v-if="form.errors[`projects.${index}.endDate`]"
|
||||
class="mt-2 text-sm text-red-600"
|
||||
@ -525,7 +547,7 @@ import { Listbox, ListboxOption, ListboxOptions, ListboxLabel, ListboxButton } f
|
||||
import { SelectorIcon, CheckIcon } from '@heroicons/vue/outline'
|
||||
import { ExclamationCircleIcon } from '@heroicons/vue/solid'
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
import FlatPickr from 'vue-flatpickr-component'
|
||||
import MonthPicker from '@/Shared/Forms/MonthPicker'
|
||||
import DynamicSection from '@/Shared/Forms/DynamicSection'
|
||||
import Combobox from '@/Shared/Forms/Combobox'
|
||||
import MultipleCombobox from '@/Shared/Forms/MultipleCombobox'
|
||||
@ -552,11 +574,11 @@ const form = useForm(`EditResume:${props.resume.id}`,{
|
||||
educations: props.resume.education ?? [],
|
||||
projects: props.resume.projects ?? [],
|
||||
technologies: props.resume.technologies.map((technology) => ({
|
||||
name: props.technologies.find((tech) => tech === technology.name),
|
||||
name: technology.name,
|
||||
level: technologyLevels.find((level) => level.level === technology.level),
|
||||
})) ?? [],
|
||||
languages: props.resume.languages.map((language) => ({
|
||||
name: languages.find((lang) => lang === language.name),
|
||||
name: language.name,
|
||||
level: languageLevels.find((level) => level.level === language.level),
|
||||
})) ?? [],
|
||||
})
|
||||
@ -606,7 +628,10 @@ function submitResume() {
|
||||
.transform((data) => ({
|
||||
user: data.user?.id,
|
||||
name: data.name,
|
||||
education: data.educations,
|
||||
education: data.educations.map(education => ({
|
||||
...education,
|
||||
endDate: education.current ? null: education.endDate,
|
||||
})),
|
||||
languages: data.languages.map(language => ({
|
||||
name: language.name,
|
||||
level: language.level.level,
|
||||
@ -615,7 +640,10 @@ function submitResume() {
|
||||
name: technology.name,
|
||||
level: technology.level.level,
|
||||
})),
|
||||
projects: data.projects,
|
||||
projects: data.projects.map(project => ({
|
||||
...project,
|
||||
endDate: project.current ? null : project.endDate,
|
||||
})),
|
||||
}))
|
||||
.put(`/resumes/${props.resume.id}`)
|
||||
}
|
||||
|
222
resources/js/Pages/Technologies.vue
Normal file
222
resources/js/Pages/Technologies.vue
Normal file
@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<InertiaHead title="Klucze" />
|
||||
<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">
|
||||
Technologie
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center py-3 px-4 text-sm font-medium leading-4 text-white bg-blumilk-600 hover:bg-blumilk-700 rounded-md border border-transparent focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 shadow-sm"
|
||||
@click="creating = true"
|
||||
>
|
||||
Dodaj technologię
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<div class="overflow-auto xl:overflow-visible">
|
||||
<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"
|
||||
>
|
||||
Technologia
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-100">
|
||||
<tr
|
||||
v-for="technology in technologies.data"
|
||||
:key="technology.id"
|
||||
class="hover:bg-blumilk-25"
|
||||
>
|
||||
<td class="px-4 py-2 text-sm text-gray-500 whitespace-nowrap">
|
||||
{{ technology.name }}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-right text-gray-500 whitespace-nowrap">
|
||||
<Menu
|
||||
as="div"
|
||||
class="inline-block relative text-left"
|
||||
>
|
||||
<MenuButton
|
||||
class="flex items-center text-gray-400 hover:text-gray-600 rounded-full focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 focus:ring-offset-gray-100"
|
||||
>
|
||||
<DotsVerticalIcon class="w-5 h-5" />
|
||||
</MenuButton>
|
||||
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-100"
|
||||
enter-from-class="transform opacity-0 scale-95"
|
||||
enter-to-class="transform opacity-100 scale-100"
|
||||
leave-active-class="transition ease-in duration-75"
|
||||
leave-from-class="transform opacity-100 scale-100"
|
||||
leave-to-class="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems
|
||||
class="absolute right-0 z-10 mt-2 w-56 bg-white rounded-md focus:outline-none ring-1 ring-black ring-opacity-5 shadow-lg origin-top-right"
|
||||
>
|
||||
<div class="py-1">
|
||||
<MenuItem
|
||||
v-slot="{ active }"
|
||||
class="flex"
|
||||
>
|
||||
<InertiaLink
|
||||
as="button"
|
||||
method="delete"
|
||||
preserve-scroll
|
||||
:href="`/technologies/${technology.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']"
|
||||
>
|
||||
<TrashIcon class="mr-2 w-5 h-5 text-red-500" />
|
||||
Usuń
|
||||
</InertiaLink>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!technologies.data.length">
|
||||
<td
|
||||
colspan="100%"
|
||||
class="py-4 text-xl leading-5 text-center text-gray-700"
|
||||
>
|
||||
Brak danych
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TransitionRoot
|
||||
as="template"
|
||||
:show="creating"
|
||||
>
|
||||
<Dialog
|
||||
is="div"
|
||||
class="overflow-y-auto fixed inset-0 z-10"
|
||||
@close="creating = false"
|
||||
>
|
||||
<div class="flex justify-center items-end px-4 pt-4 pb-20 min-h-screen text-center sm:block sm:p-0">
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="ease-out duration-300"
|
||||
enter-from="opacity-0"
|
||||
enter-to="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<DialogOverlay class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||
</TransitionChild>
|
||||
|
||||
<span class="hidden sm:inline-block sm:h-screen sm:align-middle">​</span>
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="ease-out duration-300"
|
||||
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<form
|
||||
class="inline-block relative px-4 pt-5 pb-4 text-left align-bottom bg-white rounded-lg shadow-xl transition-all transform sm:p-6 sm:my-8 sm:w-full sm:max-w-sm sm:align-middle"
|
||||
@submit.prevent="submitCreateTechnology"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<DialogTitle
|
||||
as="h3"
|
||||
class="text-lg font-medium leading-6 text-center text-gray-900 font-sembiold"
|
||||
>
|
||||
Dodaj technologię
|
||||
</DialogTitle>
|
||||
<div class="mt-5">
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px"
|
||||
>
|
||||
Nazwa
|
||||
</label>
|
||||
<div class="mt-2">
|
||||
<input
|
||||
id="name"
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
class="block w-full max-w-lg rounded-md shadow-sm sm:text-sm"
|
||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.name, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.name }"
|
||||
>
|
||||
<p
|
||||
v-if="form.errors.name"
|
||||
class="mt-2 text-sm text-red-600"
|
||||
>
|
||||
{{ form.errors.name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-6">
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
class="py-2 px-4 text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 shadow-sm"
|
||||
@click="creating = false"
|
||||
>
|
||||
Anuluj
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="form.processing"
|
||||
class="inline-flex justify-center py-2 px-4 text-base font-medium text-white bg-blumilk-600 hover:bg-blumilk-700 rounded-md border border-transparent focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 shadow-sm sm:text-sm"
|
||||
>
|
||||
Dodaj
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DotsVerticalIcon, TrashIcon } from '@heroicons/vue/solid'
|
||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
|
||||
import { ref } from 'vue'
|
||||
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
|
||||
defineProps({
|
||||
technologies: Object,
|
||||
})
|
||||
|
||||
const creating = ref(false)
|
||||
|
||||
const form = useForm({
|
||||
name: null,
|
||||
})
|
||||
|
||||
function submitCreateTechnology() {
|
||||
form.post('technologies', {
|
||||
preserveState: (page) => Object.keys(page.props.errors).length,
|
||||
preserveScroll: true,
|
||||
onSuccess: () => form.reset(),
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
18
resources/js/Shared/Forms/MonthPicker.vue
Normal file
18
resources/js/Shared/Forms/MonthPicker.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<FlatPickr :config="config" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FlatPickr from 'vue-flatpickr-component'
|
||||
import monthSelectPlugin from 'flatpickr/dist/plugins/monthSelect'
|
||||
|
||||
const config = {
|
||||
plugins: [
|
||||
new monthSelectPlugin({
|
||||
shorthand: true,
|
||||
dateFormat: 'm/Y',
|
||||
}),
|
||||
],
|
||||
}
|
||||
</script>
|
||||
|
@ -6,15 +6,31 @@
|
||||
multiple
|
||||
>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button
|
||||
<span
|
||||
v-for="(item, index) in selectedItems"
|
||||
:key="index"
|
||||
type="button"
|
||||
class="py-1 px-2 bg-gray-200 rounded-md"
|
||||
@click="selectedItems.splice(index, 1)"
|
||||
class="inline-flex items-center py-1.5 pl-3 pr-1.5 rounded-lg text-sm font-medium bg-blumilk-500 text-white"
|
||||
>
|
||||
{{ item }}
|
||||
<button
|
||||
type="button"
|
||||
class="flex-shrink-0 ml-0.5 h-5 w-5 rounded-full inline-flex items-center justify-center text-white hover:bg-blumilk-600 focus:outline-none"
|
||||
@click="selectedItems.splice(index, 1)"
|
||||
>
|
||||
<svg
|
||||
class="h-2 w-2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
viewBox="0 0 8 8"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
d="M1 1l6 6m0-6L1 7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="relative mt-2">
|
||||
<ComboboxInput
|
||||
|
@ -59,7 +59,7 @@
|
||||
>
|
||||
</InertiaLink>
|
||||
</div>
|
||||
<nav class="overflow-y-auto shrink-0 mt-5 h-full divide-y divide-blumilk-800">
|
||||
<nav class="overflow-y-auto shrink-0 mt-5 h-full space-y-5">
|
||||
<div class="px-2 space-y-1">
|
||||
<InertiaLink
|
||||
href="/"
|
||||
@ -70,10 +70,12 @@
|
||||
Strona główna
|
||||
</InertiaLink>
|
||||
</div>
|
||||
<div class="pt-3 mt-3">
|
||||
<div class="py-1 px-2 space-y-1">
|
||||
<div
|
||||
v-if="vacationNavigation.length"
|
||||
class="py-1 px-2 space-y-1"
|
||||
>
|
||||
<InertiaLink
|
||||
v-for="item in navigation"
|
||||
v-for="item in vacationNavigation"
|
||||
:key="item.name"
|
||||
:href="item.href"
|
||||
:class="[$page.component.startsWith(item.section) ? 'bg-blumilk-800 text-white' : 'text-blumilk-100 hover:text-white hover:bg-blumilk-600', 'group flex items-center px-2 py-2 text-base font-medium rounded-md']"
|
||||
@ -92,6 +94,29 @@
|
||||
</span>
|
||||
</InertiaLink>
|
||||
</div>
|
||||
<div
|
||||
v-if="miscNavigaction.length"
|
||||
class="py-1 px-2 space-y-1"
|
||||
>
|
||||
<InertiaLink
|
||||
v-for="item in miscNavigaction"
|
||||
:key="item.name"
|
||||
:href="item.href"
|
||||
:class="[$page.component.startsWith(item.section) ? 'bg-blumilk-800 text-white' : 'text-blumilk-100 hover:text-white hover:bg-blumilk-600', 'group flex items-center px-2 py-2 text-base font-medium rounded-md']"
|
||||
@click="sidebarOpen = false;"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="shrink-0 mr-4 w-6 h-6 text-blumilk-200"
|
||||
/>
|
||||
{{ item.name }}
|
||||
<span
|
||||
v-if="item.badge"
|
||||
class="py-0.5 px-2.5 ml-3 text-xs font-semibold text-gray-600 bg-gray-100 rounded-full 2xl:inline-block"
|
||||
>
|
||||
{{ item.badge }}
|
||||
</span>
|
||||
</InertiaLink>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
@ -110,7 +135,7 @@
|
||||
>
|
||||
</InertiaLink>
|
||||
</div>
|
||||
<nav class="flex overflow-y-auto flex-col flex-1 px-2 mt-5 divide-y divide-blumilk-800">
|
||||
<nav class="flex overflow-y-auto flex-col flex-1 px-2 mt-5 space-y-4">
|
||||
<InertiaLink
|
||||
href="/"
|
||||
:class="[$page.component === 'Dashboard' ? 'bg-blumilk-800 text-white' : 'text-blumilk-100 hover:text-white hover:bg-blumilk-600', 'group flex items-center px-2 py-2 mt-1 text-sm leading-6 font-medium rounded-md']"
|
||||
@ -118,9 +143,35 @@
|
||||
<HomeIcon class="shrink-0 mr-4 w-6 h-6 text-blumilk-200" />
|
||||
Strona główna
|
||||
</InertiaLink>
|
||||
<div class="pt-1 mt-1 space-y-1">
|
||||
<div
|
||||
v-if="vacationNavigation.length"
|
||||
class="pt-1 mt-1 space-y-1"
|
||||
>
|
||||
<InertiaLink
|
||||
v-for="item in navigation"
|
||||
v-for="item in vacationNavigation"
|
||||
:key="item.name"
|
||||
:href="item.href"
|
||||
:class="[$page.component.startsWith(item.section) ? 'bg-blumilk-800 text-white' : 'text-blumilk-100 hover:text-white hover:bg-blumilk-600', 'group flex items-center px-2 py-2 text-sm leading-6 font-medium rounded-md']"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="shrink-0 mr-4 w-6 h-6 text-blumilk-200"
|
||||
/>
|
||||
{{ item.name }}
|
||||
<span
|
||||
v-if="item.badge"
|
||||
class="py-0.5 px-2.5 ml-3 text-xs font-semibold text-gray-600 bg-gray-100 rounded-full 2xl:inline-block"
|
||||
>
|
||||
{{ item.badge }}
|
||||
</span>
|
||||
</InertiaLink>
|
||||
</div>
|
||||
<div
|
||||
v-if="miscNavigaction.length"
|
||||
class="pt-1 mt-1 space-y-1"
|
||||
>
|
||||
<InertiaLink
|
||||
v-for="item in miscNavigaction"
|
||||
:key="item.name"
|
||||
:href="item.href"
|
||||
:class="[$page.component.startsWith(item.section) ? 'bg-blumilk-800 text-white' : 'text-blumilk-100 hover:text-white hover:bg-blumilk-600', 'group flex items-center px-2 py-2 text-sm leading-6 font-medium rounded-md']"
|
||||
@ -295,7 +346,7 @@ import {
|
||||
DocumentTextIcon,
|
||||
AdjustmentsIcon,
|
||||
KeyIcon,
|
||||
TemplateIcon,
|
||||
TemplateIcon, BeakerIcon,
|
||||
} from '@heroicons/vue/outline'
|
||||
import { CheckIcon, ChevronDownIcon } from '@heroicons/vue/solid'
|
||||
|
||||
@ -307,7 +358,7 @@ const props = defineProps({
|
||||
|
||||
const sidebarOpen = ref(false)
|
||||
|
||||
const navigation = computed(() =>
|
||||
const vacationNavigation = computed(() =>
|
||||
[
|
||||
{
|
||||
name: 'Moje wnioski',
|
||||
@ -359,6 +410,9 @@ const navigation = computed(() =>
|
||||
icon: ClipboardListIcon,
|
||||
can: true,
|
||||
},
|
||||
].filter(item => item.can))
|
||||
|
||||
const miscNavigaction = computed(() => [
|
||||
{
|
||||
name: 'Użytkownicy',
|
||||
href: '/users',
|
||||
@ -373,6 +427,13 @@ const navigation = computed(() =>
|
||||
icon: KeyIcon,
|
||||
can: true,
|
||||
},
|
||||
{
|
||||
name: 'Technologie',
|
||||
href: '/technologies',
|
||||
section: 'Technologies',
|
||||
icon: BeakerIcon,
|
||||
can: props.auth.can.manageResumes,
|
||||
},
|
||||
{
|
||||
name: 'CV',
|
||||
href: '/resumes',
|
||||
@ -380,6 +441,5 @@ const navigation = computed(() =>
|
||||
icon: TemplateIcon,
|
||||
can: props.auth.can.manageResumes,
|
||||
},
|
||||
|
||||
].filter(item => item.can))
|
||||
].filter(item => item.can))
|
||||
</script>
|
||||
|
@ -76,5 +76,7 @@
|
||||
":recipient takes key no :key from :sender": ":recipient zabiera klucz nr :key :sender",
|
||||
"Resume has been updated.": "CV zostało zaktualizowane.",
|
||||
"Resume has been deleted.": "CV zostało usunięte.",
|
||||
"Resume has been created.": "CV zostało utworzone."
|
||||
"Resume has been created.": "CV zostało utworzone.",
|
||||
"Technology :name has been created.": "Technologia :name została utworzona.",
|
||||
"Technology :name has been deleted.": "Technologia :name została usunięta."
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use Toby\Infrastructure\Http\Controllers\LogoutController;
|
||||
use Toby\Infrastructure\Http\Controllers\MonthlyUsageController;
|
||||
use Toby\Infrastructure\Http\Controllers\ResumeController;
|
||||
use Toby\Infrastructure\Http\Controllers\SelectYearPeriodController;
|
||||
use Toby\Infrastructure\Http\Controllers\TechnologyController;
|
||||
use Toby\Infrastructure\Http\Controllers\TimesheetController;
|
||||
use Toby\Infrastructure\Http\Controllers\UserController;
|
||||
use Toby\Infrastructure\Http\Controllers\VacationCalendarController;
|
||||
@ -37,6 +38,9 @@ Route::middleware(["auth", TrackUserLastActivity::class])->group(function (): vo
|
||||
|
||||
Route::resource("resumes", ResumeController::class)
|
||||
->whereNumber("resume");
|
||||
Route::resource("technologies", TechnologyController::class)
|
||||
->only(["index", "store", "destroy"])
|
||||
->whereNumber("technology");
|
||||
|
||||
Route::get("/keys", [KeysController::class, "index"]);
|
||||
Route::post("/keys", [KeysController::class, "store"]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user