- add cv management
This commit is contained in:
parent
f32f13604f
commit
1ccc934561
95
app/Http/Controllers/Dashboard/CVController.php
Normal file
95
app/Http/Controllers/Dashboard/CVController.php
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Dashboard;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\CVRequest;
|
||||||
|
use App\Http\Resources\CVInfoCollection;
|
||||||
|
use App\Http\Resources\FullCVCollection;
|
||||||
|
use App\Http\Resources\FullCVResource;
|
||||||
|
use App\Models\CV;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Inertia\Response as InertiaResponse;
|
||||||
|
|
||||||
|
class CVController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request): InertiaResponse
|
||||||
|
{
|
||||||
|
return inertia('CV/Index', [
|
||||||
|
'cvs' => new FullCVCollection(CV::all()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(CV $cv): InertiaResponse
|
||||||
|
{
|
||||||
|
return inertia('CV/Show', [
|
||||||
|
'cv' => new FullCVResource($cv),
|
||||||
|
'cvInfo' => new CVInfoCollection($cv->info()->orderByDesc('id')->get()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): InertiaResponse
|
||||||
|
{
|
||||||
|
return inertia('CV/Create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(CVRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
CV::query()
|
||||||
|
->create([
|
||||||
|
'token' => Str::random(50),
|
||||||
|
'recipient' => $request->get('recipient'),
|
||||||
|
'email' => $request->get('email'),
|
||||||
|
'phone_number' => $request->get('phone_number'),
|
||||||
|
'locations' => ($locations = $request->get('locations')) === [''] ? [] : $locations,
|
||||||
|
'mission' => ($mission = $request->get('mission')) === [''] ? [] : $mission,
|
||||||
|
'rodo' => ($rodo =$request->get('rodo')) === '' ? null : $rodo,
|
||||||
|
'position' => $request->get('position'),
|
||||||
|
]);
|
||||||
|
return redirect()
|
||||||
|
->route('admin.cv.store')
|
||||||
|
->with('success', 'Utworzono nowe CV dla firmy ' . $request->get('recipient'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(CV $cv): InertiaResponse
|
||||||
|
{
|
||||||
|
return inertia('CV/Edit', [
|
||||||
|
'cv' => new FullCVResource($cv),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(CVRequest $request, CV $cv): RedirectResponse
|
||||||
|
{
|
||||||
|
$cv->update([
|
||||||
|
'recipient' => $request->get('recipient'),
|
||||||
|
'email' => $request->get('email'),
|
||||||
|
'phone_number' => $request->get('phone_number'),
|
||||||
|
'locations' => ($locations = $request->get('locations')) === [''] ? [] : $locations,
|
||||||
|
'mission' => ($mission = $request->get('mission')) === [''] ? [] : $mission,
|
||||||
|
'rodo' => ($rodo =$request->get('rodo')) === '' ? null : $rodo,
|
||||||
|
'position' => $request->get('position'),
|
||||||
|
]);
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('success', 'Zaktualizowano CV dla firmy ' . $request->get('recipient'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(CV $cv): InertiaResponse
|
||||||
|
{
|
||||||
|
return inertia('CV/ConfirmDelete', compact('cv'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(CV $cv): RedirectResponse
|
||||||
|
{
|
||||||
|
$name = $cv->recipient;
|
||||||
|
$cv->delete();
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('admin.cv.index')
|
||||||
|
->with('info', 'Usunięto CV dla firmy "'. $name .'"');
|
||||||
|
}
|
||||||
|
}
|
23
app/Http/Requests/CVRequest.php
Normal file
23
app/Http/Requests/CVRequest.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class CVRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'recipient' => 'required|string',
|
||||||
|
'email' => 'required|email',
|
||||||
|
'phone_number' => 'required|string',
|
||||||
|
'locations' => 'required|array',
|
||||||
|
'mission' => 'nullable|string',
|
||||||
|
'rodo' => 'nullable|string',
|
||||||
|
'position' => 'required|string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
17
app/Http/Resources/CVCollection.php
Normal file
17
app/Http/Resources/CVCollection.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class CVCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
17
app/Http/Resources/CVInfoCollection.php
Normal file
17
app/Http/Resources/CVInfoCollection.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class CVInfoCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
21
app/Http/Resources/CVInfoResource.php
Normal file
21
app/Http/Resources/CVInfoResource.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class CVInfoResource extends JsonResource
|
||||||
|
{
|
||||||
|
public static $wrap = null;
|
||||||
|
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'ip' => $this->ip,
|
||||||
|
'created_at' => $this->created_at->format('d-m-Y H:i:s'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
17
app/Http/Resources/FullCVCollection.php
Normal file
17
app/Http/Resources/FullCVCollection.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class FullCVCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
29
app/Http/Resources/FullCVResource.php
Normal file
29
app/Http/Resources/FullCVResource.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class FullCVResource extends JsonResource
|
||||||
|
{
|
||||||
|
public static $wrap = null;
|
||||||
|
|
||||||
|
public function toArray($request = null): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'token' => $this->token,
|
||||||
|
'email' => $this->email,
|
||||||
|
'recipient' => $this->recipient,
|
||||||
|
'phone' => new PhoneResource($this->resource),
|
||||||
|
'locations' => $this->locations,
|
||||||
|
'views' => $this->resource->info()->select('id')->get()->count(),
|
||||||
|
'registeredViews' => $this->views,
|
||||||
|
'mission' => explode(PHP_EOL, $this->mission ?? '', 5),
|
||||||
|
'rodo' => $this->rodo,
|
||||||
|
'position' => $this->position,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
37
resources/js/Pages/CV/ConfirmDelete.vue
Normal file
37
resources/js/Pages/CV/ConfirmDelete.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<script setup>
|
||||||
|
import { router } from '@inertiajs/vue3';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
cv: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
router.delete(`/dashboard/cv/${props.cv.token}/delete`);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InertiaHead title="Usuwanie CV" />
|
||||||
|
<div class="p-4">
|
||||||
|
<header class="pb-4">
|
||||||
|
<h1 class="text-3xl font-roboto font-light">Usuwanie CV</h1>
|
||||||
|
</header>
|
||||||
|
<div class="max-w-[600px]">
|
||||||
|
<p class="mb-4">Na pewno usunąć CV dla firmy {{ cv.recipient }}?</p>
|
||||||
|
<div class="grid grid-cols-3 gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
href="/dashboard/cv"
|
||||||
|
class="col-span-1 flex justify-center items-center gap-3 w-full px-2 py-1 border-t-4 border-b-4 border-transparent hover:border-b-black"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'backward']" />Anuluj</InertiaLink>
|
||||||
|
<button
|
||||||
|
@click.prevent="confirmDelete"
|
||||||
|
class="col-span-2 flex justify-center items-center gap-3 w-full px-2 py-1 rounded-md bg-red-600 border-4 border-red-600 text-white text-lg hover:bg-transparent hover:text-red-600"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'trash']" /><span class="whitespace-nowrap overflow-hidden overflow-ellipsis">Usuń CV dla firmy {{ cv.recipient }}</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
113
resources/js/Pages/CV/Create.vue
Normal file
113
resources/js/Pages/CV/Create.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useForm } from '@inertiajs/inertia-vue3';
|
||||||
|
import Input from '../../Share/Components/Input.vue';
|
||||||
|
|
||||||
|
const locations = ref([]);
|
||||||
|
const locationsToString = computed({
|
||||||
|
get: () => locations.value.join(', '),
|
||||||
|
set: (val) => {
|
||||||
|
val = val.replace(', ', ',').replace(' , ', ',').replace(' ,', ',');
|
||||||
|
val = val.split(',');
|
||||||
|
val.forEach((element, key) => {
|
||||||
|
val[key] = element.trim();
|
||||||
|
});
|
||||||
|
locations.value = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mission = ref([]);
|
||||||
|
const missionToString = computed({
|
||||||
|
get: () => mission.value.join("\n"),
|
||||||
|
set: (value) => {
|
||||||
|
mission.value = value.split("\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
recipient: null,
|
||||||
|
email: null,
|
||||||
|
phone_number: null,
|
||||||
|
locations: locations,
|
||||||
|
mission: missionToString,
|
||||||
|
rodo: null,
|
||||||
|
position: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
function createCV() {
|
||||||
|
form.post('/dashboard/cv');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InertiaHead title="Nowe dane do CV" />
|
||||||
|
<div class="p-4">
|
||||||
|
<header class="pb-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
href="/dashboard/cv"
|
||||||
|
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||||
|
title="Wróć do listy CV"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||||
|
<h1 class="text-3xl font-roboto font-light">Nowe dane do CV</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div>
|
||||||
|
<form class="flex flex-col gap-4" @submit.prevent="createCV">
|
||||||
|
<Input
|
||||||
|
id="recipient"
|
||||||
|
label="Firma gdzie jest składane CV"
|
||||||
|
placeholder="Oki doki sp. z.o.o"
|
||||||
|
v-model="form.recipient"
|
||||||
|
:error="form.errors.recipient"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
label="E-mail"
|
||||||
|
placeholder="Adres e-mail wyświetlany na CV"
|
||||||
|
v-model="form.email"
|
||||||
|
:error="form.errors.email"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
label="Numer telefonu"
|
||||||
|
placeholder="+48 123 456 789"
|
||||||
|
v-model="form.phone_number"
|
||||||
|
:error="form.errors.phone_number"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="locations"
|
||||||
|
label="Lokalizacje"
|
||||||
|
placeholder="Miejsca pracy."
|
||||||
|
v-model="locationsToString"
|
||||||
|
:error="form.errors.locations"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="position"
|
||||||
|
label="Stanowisko"
|
||||||
|
placeholder="Stanowisko na jakie jest rekrutacja."
|
||||||
|
v-model="form.position"
|
||||||
|
:error="form.errors.position"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="mission"
|
||||||
|
type="textarea"
|
||||||
|
label="Misja - wstęp"
|
||||||
|
placeholder="Krótki opis, list motywacyjny."
|
||||||
|
v-model="form.mission"
|
||||||
|
:error="form.errors.mission"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="rodo"
|
||||||
|
type="textarea"
|
||||||
|
label="RODO"
|
||||||
|
placeholder="Klauzula informacyjna RODO"
|
||||||
|
v-model="form.rodo"
|
||||||
|
:error="form.errors.rodo"
|
||||||
|
/>
|
||||||
|
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Utwórz nowe CV</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
126
resources/js/Pages/CV/Edit.vue
Normal file
126
resources/js/Pages/CV/Edit.vue
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useForm } from '@inertiajs/inertia-vue3';
|
||||||
|
import Input from '../../Share/Components/Input.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
cv: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const locations = ref(props.cv.locations);
|
||||||
|
const locationsToString = computed({
|
||||||
|
get: () => locations.value.join(', '),
|
||||||
|
set: (val) => {
|
||||||
|
val = val.replace(', ', ',').replace(' , ', ',').replace(' ,', ',');
|
||||||
|
val = val.split(',');
|
||||||
|
val.forEach((element, key) => {
|
||||||
|
val[key] = element.trim();
|
||||||
|
});
|
||||||
|
locations.value = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mission = ref(props.cv.mission);
|
||||||
|
const missionToString = computed({
|
||||||
|
get: () => mission.value.join("\n"),
|
||||||
|
set: (value) => {
|
||||||
|
mission.value = value.split("\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
recipient: props.cv.recipient,
|
||||||
|
email: props.cv.email,
|
||||||
|
phone_number: props.cv.phone.formattedNumber,
|
||||||
|
locations: locations,
|
||||||
|
mission: missionToString,
|
||||||
|
rodo: props.cv.rodo,
|
||||||
|
position: props.cv.position,
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateCV() {
|
||||||
|
form.put(`/dashboard/cv/${props.cv.token}`);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InertiaHead title="Edycja danych do CV" />
|
||||||
|
<div class="p-4">
|
||||||
|
<header class="flex items-center justify-between pb-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
href="/dashboard/cv"
|
||||||
|
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||||
|
title="Wróć do listy CV"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||||
|
<h1 class="text-3xl font-roboto font-light">Edycja danych do CV</h1>
|
||||||
|
</div>
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
:href="`/dashboard/cv/${cv.token}/delete`"
|
||||||
|
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||||
|
title="Usuń CV"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'trash']" />Usuń</InertiaLink>
|
||||||
|
</header>
|
||||||
|
<div>
|
||||||
|
<form class="flex flex-col gap-4" @submit.prevent="updateCV">
|
||||||
|
<Input
|
||||||
|
id="recipient"
|
||||||
|
label="Firma gdzie jest składane CV"
|
||||||
|
placeholder="Oki doki sp. z.o.o"
|
||||||
|
v-model="form.recipient"
|
||||||
|
:error="form.errors.recipient"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
label="E-mail"
|
||||||
|
placeholder="Adres e-mail wyświetlany na CV"
|
||||||
|
v-model="form.email"
|
||||||
|
:error="form.errors.email"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
label="Numer telefonu"
|
||||||
|
placeholder="+48 123 456 789"
|
||||||
|
v-model="form.phone_number"
|
||||||
|
:error="form.errors.phone_number"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="locations"
|
||||||
|
label="Lokalizacje"
|
||||||
|
placeholder="Miejsca pracy."
|
||||||
|
v-model="locationsToString"
|
||||||
|
:error="form.errors.locations"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="position"
|
||||||
|
label="Stanowisko"
|
||||||
|
placeholder="Stanowisko na jakie jest rekrutacja."
|
||||||
|
v-model="form.position"
|
||||||
|
:error="form.errors.position"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="mission"
|
||||||
|
type="textarea"
|
||||||
|
label="Misja - wstęp"
|
||||||
|
placeholder="Krótki opis, list motywacyjny."
|
||||||
|
v-model="form.mission"
|
||||||
|
:error="form.errors.mission"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="rodo"
|
||||||
|
type="textarea"
|
||||||
|
label="RODO"
|
||||||
|
placeholder="Klauzula informacyjna RODO"
|
||||||
|
v-model="form.rodo"
|
||||||
|
:error="form.errors.rodo"
|
||||||
|
/>
|
||||||
|
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Edytuj CV</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
96
resources/js/Pages/CV/Index.vue
Normal file
96
resources/js/Pages/CV/Index.vue
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
cvs: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function copySlug(slug) {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.value = slug;
|
||||||
|
input.select();
|
||||||
|
input.setSelectionRange(0, 99999);
|
||||||
|
|
||||||
|
navigator
|
||||||
|
.clipboard
|
||||||
|
.writeText(input.value)
|
||||||
|
.then((value, reason) => { });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InertiaHead title="Lista CV" />
|
||||||
|
<div class="p-4">
|
||||||
|
<header class="flex justify-between items-center pb-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
href="/dashboard"
|
||||||
|
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||||
|
title="Wróc do dashboard"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||||
|
<h1 class="text-3xl font-roboto font-light">Lista CV</h1>
|
||||||
|
</div>
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
href="/dashboard/cv/create"
|
||||||
|
class="bg-blue-400 hover:bg-blue-500 text-white px-2.5 py-1 rounded-full"
|
||||||
|
title="Dodaj nowe dane dla CV"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'plus']" /></InertiaLink>
|
||||||
|
</header>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table v-if="cvs.data.length" class="w-full min-w-[600px] border-separate border-spacing-y-2">
|
||||||
|
<colgroup>
|
||||||
|
<col class="w-min" />
|
||||||
|
</colgroup>
|
||||||
|
<thead class="text-left bg-gray-100">
|
||||||
|
<th class="p-2 text-center">ID</th>
|
||||||
|
<th class="p-2">Token</th>
|
||||||
|
<th class="w-[100px] p-2 whitespace-nowrap">Firma</th>
|
||||||
|
<th class="hidden md:table-cell p-2">Lokalizacje</th>
|
||||||
|
<th class="p-2">Telefon</th>
|
||||||
|
<th class="p-2"></th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<InertiaLink
|
||||||
|
as="tr"
|
||||||
|
v-for="(cv, key) in cvs.data"
|
||||||
|
:key="key"
|
||||||
|
class="px-3 py-2 bg-white hover:bg-neutral-200 rounded-md z-10"
|
||||||
|
:href="`/dashboard/cv/${cv.token}`">
|
||||||
|
<td class="p-2 text-center">#{{ cv.id }}</td>
|
||||||
|
<td class="p-2"
|
||||||
|
><button
|
||||||
|
class="bg-gray-50 p-1 rounded-md hover:bg-gray-100"
|
||||||
|
@click.prevent="copySlug(cv.token)"
|
||||||
|
:title="cv.token">{{ cv.token.slice(0, 7) }}</button></td>
|
||||||
|
<td class="max-w-[150px] md:max-w-[250px] p-2 whitespace-nowrap overflow-hidden overflow-ellipsis" :title="cv.recipient">{{ cv.recipient }}</td>
|
||||||
|
<td class="hidden md:table-cell p-2">{{ cv.locations.join(' / ') }}</td>
|
||||||
|
<td class="p-2">{{ cv.phone.formattedNumber }}</td>
|
||||||
|
<td class="flex items-center justify-center gap-2 p-3 z-50">
|
||||||
|
<a
|
||||||
|
class="px-3 py-3 text-blue-600 hover:text-blue-800"
|
||||||
|
:href="`https://cv.kamilcraft.com/show/${cv.token}`"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
title="Przekieruj do CV"><FontAwesomeIcon :icon="['fas', 'arrow-up-right-from-square']" /></a>
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
class="px-3 py-3 text-lime-600 hover:text-lime-800 border-t-2 border-b-2 border-transparent hover:border-b-lime-600"
|
||||||
|
:href="`/dashboard/cv/${cv.token}/edit`"
|
||||||
|
title="Edytuj projekt"><FontAwesomeIcon :icon="['fas', 'pen-to-square']" /></InertiaLink>
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
class="px-3 py-3 text-red-600 hover:text-red-800"
|
||||||
|
:href="`/dashboard/cv/${cv.token}/delete`"
|
||||||
|
title="Usuń projekt z listy"><FontAwesomeIcon :icon="['fas', 'trash']" /></InertiaLink>
|
||||||
|
</td>
|
||||||
|
</InertiaLink>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div v-else>
|
||||||
|
Pusta lista
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
124
resources/js/Pages/CV/Show.vue
Normal file
124
resources/js/Pages/CV/Show.vue
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
cv: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
cvInfo: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InertiaHead title="Szczegóły CV" />
|
||||||
|
<div class="p-4">
|
||||||
|
<header class="flex justify-between items-center pb-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
href="/dashboard/cv"
|
||||||
|
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||||
|
title="Wróć do listy CV"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||||
|
<h1 class="text-3xl font-roboto font-light">Szczegóły CV</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<a
|
||||||
|
class="px-2 py-1 text-blue-600 hover:text-blue-800"
|
||||||
|
:href="`https://cv.kamilcraft.com/show/${cv.token}`"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
title="Przekieruj do CV"><FontAwesomeIcon :icon="['fas', 'arrow-up-right-from-square']" /></a>
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
:href="`/dashboard/cv/${cv.token}/edit`"
|
||||||
|
class="flex items-center gap-2 px-2 py-1 text-lime-600 hover:text-white hover:bg-lime-600 rounded-md"
|
||||||
|
title="Usuń CV"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'pen-to-square']" />Edytuj</InertiaLink>
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
:href="`/dashboard/cv/${cv.token}/delete`"
|
||||||
|
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||||
|
title="Usuń CV"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'trash']" />Usuń</InertiaLink>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="mb-4">
|
||||||
|
<header>
|
||||||
|
<h2 class="text-2xl font-roboto font-light pb-3">Podstawowe informacje</h2>
|
||||||
|
</header>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Token</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white whitespace-nowrap overflow-hidden overflow-ellipsis">{{ cv.token }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Firma</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.recipient }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Stanowisko</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.position }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">E-mail</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.email }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Numer telefonu</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.phone.formattedNumber }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Lokalizacje</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.locations.join(' / ') }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Zachowane wyświetlenia</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.views }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Łączne wyświetlenia</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.registeredViews }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<header>
|
||||||
|
<h2 class="text-2xl font-roboto font-light pb-3">Opisy</h2>
|
||||||
|
</header>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Misja</div>
|
||||||
|
<div v-if="cv.mission.length > 0 && cv.mission[0] !== ''" class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">
|
||||||
|
<p
|
||||||
|
v-for="(mission, key) in cv.mission"
|
||||||
|
:key="key">{{ mission }}</p>
|
||||||
|
</div>
|
||||||
|
<p v-else class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">[Wartość domyślna]</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">RODO</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.rodo ?? '[Wartość domyślna]' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<header>
|
||||||
|
<h2 class="text-2xl font-roboto font-light pb-3">Lista wejść</h2>
|
||||||
|
</header>
|
||||||
|
<ul v-if="cvInfo.data.length" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
|
||||||
|
<li
|
||||||
|
v-for="(info, key) in cvInfo.data"
|
||||||
|
:key="key"
|
||||||
|
class="flex flex-col px-3 py-2 bg-white rounded-md">
|
||||||
|
<span>#{{ info.id }} {{ info.ip }}</span>
|
||||||
|
<span class="text-xs">{{ info.created_at }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div v-else>
|
||||||
|
Brak wyświetleń
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -26,8 +26,9 @@ defineProps({
|
|||||||
>Kamil<span class="logo-green">Craft</span></div>
|
>Kamil<span class="logo-green">Craft</span></div>
|
||||||
</InertiaLink>
|
</InertiaLink>
|
||||||
<nav>
|
<nav>
|
||||||
<ul class="flex gap-3 items-center">
|
<ul class="flex gap-3 items-center font-bold">
|
||||||
<li></li>
|
<li><InertiaLink class="text-white active:text-kamilcraft-green hover:text-black hover:underline" href="/dashboard">Dashboard</InertiaLink></li>
|
||||||
|
<li><InertiaLink class="text-white active:text-kamilcraft-green hover:text-black hover:underline" href="/dashboard/cv">CV</InertiaLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,6 +7,26 @@ use Illuminate\Support\Facades\Route;
|
|||||||
Route::name('admin.')->group(function () {
|
Route::name('admin.')->group(function () {
|
||||||
Route::namespace('Dashboard')->middleware('auth')->group(function () {
|
Route::namespace('Dashboard')->middleware('auth')->group(function () {
|
||||||
Route::get('', 'AdminPanelController')->name('home');
|
Route::get('', 'AdminPanelController')->name('home');
|
||||||
|
Route::name('cv.')->prefix('cv')->group(function () {
|
||||||
|
Route::get('', 'CVController@index')
|
||||||
|
->name('index');
|
||||||
|
Route::get('create', 'CVController@create')
|
||||||
|
->name('create');
|
||||||
|
Route::post('', 'CVController@store')
|
||||||
|
->name('store');
|
||||||
|
Route::get('{cv}', 'CVController@show')
|
||||||
|
->name('show');
|
||||||
|
Route::post('', 'CVController@store')
|
||||||
|
->name('store');
|
||||||
|
Route::get('{cv}/edit', 'CVController@edit')
|
||||||
|
->name('edit');
|
||||||
|
Route::put('{cv}', 'CVController@update')
|
||||||
|
->name('update');
|
||||||
|
Route::get('{cv}/delete', 'CVController@delete')
|
||||||
|
->name('delete');
|
||||||
|
Route::delete('{cv}/delete', 'CVController@destroy')
|
||||||
|
->name('destroy');
|
||||||
|
});
|
||||||
Route::name('category.')->prefix('category')->group(function () {
|
Route::name('category.')->prefix('category')->group(function () {
|
||||||
Route::get('create', 'CategoryController@create')
|
Route::get('create', 'CategoryController@create')
|
||||||
->name('create');
|
->name('create');
|
||||||
|
5
tailwind.config.js
vendored
5
tailwind.config.js
vendored
@ -10,10 +10,11 @@ module.exports = {
|
|||||||
fontFamily: {
|
fontFamily: {
|
||||||
'arial': ['Arial', 'sans-serif'],
|
'arial': ['Arial', 'sans-serif'],
|
||||||
'roboto': ['Roboto', 'sans-serif'],
|
'roboto': ['Roboto', 'sans-serif'],
|
||||||
'thasadith': ['Thasadith', 'sans-serif']
|
'thasadith': ['Thasadith', 'sans-serif'],
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
'logo-green': 'rgb(var(--color-logo-green) / <alpha-value>)'
|
'logo-green': 'rgb(var(--color-logo-green) / <alpha-value>)',
|
||||||
|
'kamilcraft-green': 'rgb(var(--color-logo-green) / <alpha-value>)',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user