This commit is contained in:
Adrian Hopek 2022-05-10 12:19:58 +02:00
parent 5c55f1860a
commit 22c5f7bb54
10 changed files with 163 additions and 76 deletions

View File

@ -30,7 +30,7 @@ class ResumeRequest extends FormRequest
"technologies.*.level" => ["required", Rule::in(1, 2, 3, 4, 5)], "technologies.*.level" => ["required", Rule::in(1, 2, 3, 4, 5)],
"projects.*.description" => ["required"], "projects.*.description" => ["required"],
"projects.*.technologies" => ["required"], "projects.*.technologies" => ["exists:technologies,name", "distinct"],
"projects.*.startDate" => ["required", "date_format:Y-m-d"], "projects.*.startDate" => ["required", "date_format:Y-m-d"],
"projects.*.endDate" => ["required", "date_format:Y-m-d", "after:startDate"], "projects.*.endDate" => ["required", "date_format:Y-m-d", "after:startDate"],
"projects.*.tasks" => ["required"], "projects.*.tasks" => ["required"],

View File

@ -17,6 +17,10 @@ class ResumeResource extends JsonResource
"user" => new SimpleUserResource($this->user), "user" => new SimpleUserResource($this->user),
"name" => $this->name, "name" => $this->name,
"description" => $this->description, "description" => $this->description,
"educationCount" => $this->education->count(),
"languageCount" => $this->languages->count(),
"technologyCount" => $this->technologies->count(),
"projectCount" => $this->projects->count(),
"createdAt" => $this->created_at->toDisplayString(), "createdAt" => $this->created_at->toDisplayString(),
"updatedAt" => $this->updated_at->toDisplayString(), "updatedAt" => $this->updated_at->toDisplayString(),
]; ];

View File

@ -17,7 +17,6 @@ class ResumeFactory extends Factory
{ {
return [ return [
"name" => fn(array $attr) => empty($attr["user_id"]) ? $this->faker->name : null, "name" => fn(array $attr) => empty($attr["user_id"]) ? $this->faker->name : null,
"description" => $this->faker->boolean(30) ? $this->faker->sentence : null,
"education" => $this->generateEducation(), "education" => $this->generateEducation(),
"languages" => $this->generateLanguages(), "languages" => $this->generateLanguages(),
"technologies" => $this->generateTechnologies(), "technologies" => $this->generateTechnologies(),

View File

@ -14,7 +14,6 @@ return new class() extends Migration {
$table->id(); $table->id();
$table->foreignIdFor(User::class)->nullable()->constrained()->cascadeOnDelete(); $table->foreignIdFor(User::class)->nullable()->constrained()->cascadeOnDelete();
$table->string("name")->nullable(); $table->string("name")->nullable();
$table->string("description")->nullable();
$table->json("education"); $table->json("education");
$table->json("languages"); $table->json("languages");
$table->json("technologies"); $table->json("technologies");

View File

@ -263,11 +263,25 @@
<template #form="{ element, index }"> <template #form="{ element, index }">
<div class="gap-4 md:grid md:grid-cols-2 "> <div class="gap-4 md:grid md:grid-cols-2 ">
<div class="py-4"> <div class="py-4">
<label
:for="`language-${index}-level`"
class="block text-sm font-medium text-gray-700"
>
Język
</label>
<div class="mt-2">
<Combobox <Combobox
:id="`language-${index}-level`"
v-model="element.name" v-model="element.name"
label="Język"
:items="languages" :items="languages"
/> />
<p
v-if="form.errors[`languages.${index}.name`]"
class="mt-2 text-sm text-red-600"
>
{{ form.errors[`languages.${index}.name`] }}
</p>
</div>
</div> </div>
<div class="py-4"> <div class="py-4">
<label <label
@ -313,11 +327,25 @@
<template #form="{ element, index }"> <template #form="{ element, index }">
<div class="gap-4 md:grid md:grid-cols-2 "> <div class="gap-4 md:grid md:grid-cols-2 ">
<div class="py-4"> <div class="py-4">
<label
:for="`technology-${index}-level`"
class="block text-sm font-medium text-gray-700"
>
Technologia
</label>
<div class="mt-2">
<Combobox <Combobox
:id="`technology-${index}-level`"
v-model="element.name" v-model="element.name"
label="Technologia"
:items="technologies" :items="technologies"
/> />
<p
v-if="form.errors[`technologies.${index}.name`]"
class="mt-2 text-sm text-red-600"
>
{{ form.errors[`technologies.${index}.name`] }}
</p>
</div>
</div> </div>
<div class="py-4"> <div class="py-4">
<label <label
@ -367,6 +395,7 @@
<textarea <textarea
:id="`project-description-${index}`" :id="`project-description-${index}`"
v-model="element.description" v-model="element.description"
rows="5"
class="block w-full rounded-md shadow-sm sm:text-sm" 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[`projects.${index}.description`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.description`] }" :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`projects.${index}.description`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.description`] }"
/> />
@ -386,13 +415,11 @@
Technologie Technologie
</label> </label>
<div class="mt-1 sm:mt-0"> <div class="mt-1 sm:mt-0">
<input <MultipleCombobox
:id="`project-technologies-${index}`" :id="`project-technologies-${index}`"
v-model="element.technologies" v-model="element.technologies"
type="text" :items="technologies"
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[`projects.${index}.technologies`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.technologies`] }"
>
<p <p
v-if="form.errors[`projects.${index}.technologies`]" v-if="form.errors[`projects.${index}.technologies`]"
class="mt-2 text-sm text-red-600" class="mt-2 text-sm text-red-600"
@ -458,6 +485,7 @@
<textarea <textarea
:id="`project-tasks-${index}`" :id="`project-tasks-${index}`"
v-model="element.tasks" v-model="element.tasks"
rows="5"
class="block w-full rounded-md shadow-sm sm:text-sm" 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[`projects.${index}.tasks`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.tasks`] }" :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`projects.${index}.tasks`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.tasks`] }"
/> />
@ -495,10 +523,12 @@
<script setup> <script setup>
import { Listbox, ListboxOption, ListboxOptions, ListboxLabel, ListboxButton } from '@headlessui/vue' import { Listbox, ListboxOption, ListboxOptions, ListboxLabel, ListboxButton } from '@headlessui/vue'
import { SelectorIcon, CheckIcon } from '@heroicons/vue/outline' import { SelectorIcon, CheckIcon } from '@heroicons/vue/outline'
import { ExclamationCircleIcon } from '@heroicons/vue/solid'
import { useForm } from '@inertiajs/inertia-vue3' import { useForm } from '@inertiajs/inertia-vue3'
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
import DynamicSection from '@/Shared/Forms/DynamicSection' import DynamicSection from '@/Shared/Forms/DynamicSection'
import Combobox from '@/Shared/Forms/Combobox' import Combobox from '@/Shared/Forms/Combobox'
import MultipleCombobox from '@/Shared/Forms/MultipleCombobox'
import LevelPicker from '@/Shared/Forms/LevelPicker' import LevelPicker from '@/Shared/Forms/LevelPicker'
import useLevels from '@/Composables/useLevels' import useLevels from '@/Composables/useLevels'
@ -527,7 +557,7 @@ const form = useForm({
function addProject() { function addProject() {
form.projects.push({ form.projects.push({
description: null, description: null,
technologies: null, technologies: [],
tasks: null, tasks: null,
startDate: null, startDate: null,
endDate: null, endDate: null,

View File

@ -263,9 +263,16 @@
<template #form="{ element, index }"> <template #form="{ element, index }">
<div class="gap-4 md:grid md:grid-cols-2 "> <div class="gap-4 md:grid md:grid-cols-2 ">
<div class="py-4"> <div class="py-4">
<label
:for="`language-${index}-level`"
class="block text-sm font-medium text-gray-700"
>
Język
</label>
<div class="mt-2">
<Combobox <Combobox
:id="`language-${index}-level`"
v-model="element.name" v-model="element.name"
label="Język"
:items="languages" :items="languages"
/> />
<p <p
@ -275,6 +282,7 @@
{{ form.errors[`languages.${index}.name`] }} {{ form.errors[`languages.${index}.name`] }}
</p> </p>
</div> </div>
</div>
<div class="py-4"> <div class="py-4">
<label <label
:for="`language-level-${index}`" :for="`language-level-${index}`"
@ -319,9 +327,16 @@
<template #form="{ element, index }"> <template #form="{ element, index }">
<div class="gap-4 md:grid md:grid-cols-2 "> <div class="gap-4 md:grid md:grid-cols-2 ">
<div class="py-4"> <div class="py-4">
<label
:for="`technology-${index}-level`"
class="block text-sm font-medium text-gray-700"
>
Technologia
</label>
<div class="mt-2">
<Combobox <Combobox
:id="`technology-${index}-level`"
v-model="element.name" v-model="element.name"
label="Technologia"
:items="technologies" :items="technologies"
/> />
<p <p
@ -331,6 +346,7 @@
{{ form.errors[`technologies.${index}.name`] }} {{ form.errors[`technologies.${index}.name`] }}
</p> </p>
</div> </div>
</div>
<div class="py-4"> <div class="py-4">
<label <label
:for="`technology-level-${index}`" :for="`technology-level-${index}`"
@ -379,6 +395,7 @@
<textarea <textarea
:id="`project-description-${index}`" :id="`project-description-${index}`"
v-model="element.description" v-model="element.description"
rows="5"
class="block w-full rounded-md shadow-sm sm:text-sm" 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[`projects.${index}.description`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.description`] }" :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`projects.${index}.description`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.description`] }"
/> />
@ -398,13 +415,11 @@
Technologie Technologie
</label> </label>
<div class="mt-1 sm:mt-0"> <div class="mt-1 sm:mt-0">
<input <MultipleCombobox
:id="`project-technologies-${index}`" :id="`project-technologies-${index}`"
v-model="element.technologies" v-model="element.technologies"
type="text" :items="technologies"
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[`projects.${index}.technologies`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.technologies`] }"
>
<p <p
v-if="form.errors[`projects.${index}.technologies`]" v-if="form.errors[`projects.${index}.technologies`]"
class="mt-2 text-sm text-red-600" class="mt-2 text-sm text-red-600"
@ -470,7 +485,7 @@
<textarea <textarea
:id="`project-tasks-${index}`" :id="`project-tasks-${index}`"
v-model="element.tasks" v-model="element.tasks"
rows="3" rows="5"
class="block w-full rounded-md shadow-sm sm:text-sm" 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[`projects.${index}.tasks`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.tasks`] }" :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`projects.${index}.tasks`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`projects.${index}.tasks`] }"
/> />
@ -513,6 +528,7 @@ import { useForm } from '@inertiajs/inertia-vue3'
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
import DynamicSection from '@/Shared/Forms/DynamicSection' import DynamicSection from '@/Shared/Forms/DynamicSection'
import Combobox from '@/Shared/Forms/Combobox' import Combobox from '@/Shared/Forms/Combobox'
import MultipleCombobox from '@/Shared/Forms/MultipleCombobox'
import LevelPicker from '@/Shared/Forms/LevelPicker' import LevelPicker from '@/Shared/Forms/LevelPicker'
import useLevels from '@/Composables/useLevels' import useLevels from '@/Composables/useLevels'
@ -548,7 +564,7 @@ const form = useForm({
function addProject() { function addProject() {
form.projects.push({ form.projects.push({
description: null, description: null,
technologies: null, technologies: [],
tasks: null, tasks: null,
startDate: null, startDate: null,
endDate: null, endDate: null,

View File

@ -25,12 +25,6 @@
> >
Użytkownik Użytkownik
</th> </th>
<th
scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
>
Opis
</th>
<th <th
scope="col" scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
@ -43,6 +37,30 @@
> >
Data aktualizacji Data aktualizacji
</th> </th>
<th
scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
>
Szkoły
</th>
<th
scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
>
Języki
</th>
<th
scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
>
Technologie
</th>
<th
scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
>
Projekty
</th>
<th <th
scope="col" scope="col"
class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap"
@ -76,18 +94,27 @@
</div> </div>
</div> </div>
<template v-else> <template v-else>
{{ resume.name }} <span class="text-sm font-medium text-gray-900 break-all">{{ resume.name }}</span>
</template> </template>
</td> </td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ resume.description ?? '-' }}
</td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap"> <td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ resume.createdAt }} {{ resume.createdAt }}
</td> </td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap"> <td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ resume.updatedAt }} {{ resume.updatedAt }}
</td> </td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ resume.educationCount }}
</td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ resume.languageCount }}
</td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ resume.technologyCount }}
</td>
<td class="p-4 text-sm text-gray-500 whitespace-nowrap">
{{ resume.projectCount }}
</td>
<td class="p-4 text-sm text-right text-gray-500 whitespace-nowrap"> <td class="p-4 text-sm text-right text-gray-500 whitespace-nowrap">
<Menu <Menu
as="div" as="div"

View File

@ -3,11 +3,9 @@
as="div" as="div"
nullable nullable
> >
<ComboboxLabel class="block text-sm font-medium text-gray-700"> <div class="relative">
{{ label }}
</ComboboxLabel>
<div class="relative mt-2">
<ComboboxInput <ComboboxInput
:id="id"
class="w-full h-12 rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-blumilk-500 focus:outline-none focus:ring-1 focus:ring-blumilk-500 sm:text-sm" class="w-full h-12 rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-blumilk-500 focus:outline-none focus:ring-1 focus:ring-blumilk-500 sm:text-sm"
@change="query = $event.target.value" @change="query = $event.target.value"
/> />
@ -51,14 +49,13 @@ import {
Combobox, Combobox,
ComboboxButton, ComboboxButton,
ComboboxInput, ComboboxInput,
ComboboxLabel,
ComboboxOption, ComboboxOption,
ComboboxOptions, ComboboxOptions,
} from '@headlessui/vue' } from '@headlessui/vue'
const props = defineProps({ const props = defineProps({
label: null,
items: Array, items: Array,
id: String,
}) })
const query = ref('') const query = ref('')

View File

@ -23,7 +23,6 @@
</button> </button>
<Disclosure <Disclosure
v-slot="{ open }" v-slot="{ open }"
:default-open="false"
as="div" as="div"
class="flex-1 border border-gray-200" class="flex-1 border border-gray-200"
> >

View File

@ -1,21 +1,27 @@
<template> <template>
<Combobox <Combobox
v-model="selectedItems"
as="div" as="div"
nullable nullable
multiple multiple
> >
<ComboboxLabel class="block text-sm font-medium text-gray-700"> <div class="flex flex-wrap gap-3">
{{ label }} <button
</ComboboxLabel> 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)"
>
{{ item }}
</button>
</div>
<div class="relative mt-2"> <div class="relative mt-2">
<ComboboxInput <ComboboxInput
as="template" :id="id"
class="w-full h-12 rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-blumilk-500 focus:outline-none focus:ring-1 focus:ring-blumilk-500 sm:text-sm" class="w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-blumilk-500 focus:outline-none focus:ring-1 focus:ring-blumilk-500 sm:text-sm"
:display-value="(item) => item"
@change="query = $event.target.value" @change="query = $event.target.value"
> />
<span>aee</span>
</ComboboxInput>
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none"> <ComboboxButton class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
<SelectorIcon class="h-5 w-5 text-gray-400" /> <SelectorIcon class="h-5 w-5 text-gray-400" />
</ComboboxButton> </ComboboxButton>
@ -33,7 +39,7 @@
> >
<li :class="['relative cursor-default select-none py-2 pl-3 pr-9', active ? 'bg-blumilk-600 text-white' : 'text-gray-900']"> <li :class="['relative cursor-default select-none py-2 pl-3 pr-9', active ? 'bg-blumilk-600 text-white' : 'text-gray-900']">
<span :class="['block truncate', selected && 'font-semibold']"> <span :class="['block truncate', selected && 'font-semibold']">
{{ item.name }} {{ item }}
</span> </span>
<span <span
@ -56,21 +62,31 @@ import {
Combobox, Combobox,
ComboboxButton, ComboboxButton,
ComboboxInput, ComboboxInput,
ComboboxLabel,
ComboboxOption, ComboboxOption,
ComboboxOptions, ComboboxOptions,
} from '@headlessui/vue' } from '@headlessui/vue'
const props = defineProps({ const props = defineProps({
label: null,
items: Array, items: Array,
modelValue: Array,
id: String,
}) })
const emit = defineEmits(['update:modelValue'])
const query = ref('') const query = ref('')
const selectedItems = computed({
get: () => props.modelValue,
set: (value) => {
query.value = ''
emit('update:modelValue', value)
},
})
const filteredItems = computed(() => const filteredItems = computed(() =>
query.value === '' query.value === ''
? props.items ? props.items
: props.items.filter((item) => item.name.toLowerCase().includes(query.value.toLowerCase())), : props.items.filter((item) => item.toLowerCase().includes(query.value.toLowerCase())),
) )
</script> </script>