wip
This commit is contained in:
		| @@ -30,7 +30,7 @@ class ResumeRequest extends FormRequest | ||||
|             "technologies.*.level" => ["required", Rule::in(1, 2, 3, 4, 5)], | ||||
|  | ||||
|             "projects.*.description" => ["required"], | ||||
|             "projects.*.technologies" => ["required"], | ||||
|             "projects.*.technologies" => ["exists:technologies,name", "distinct"], | ||||
|             "projects.*.startDate" => ["required", "date_format:Y-m-d"], | ||||
|             "projects.*.endDate" => ["required", "date_format:Y-m-d", "after:startDate"], | ||||
|             "projects.*.tasks" => ["required"], | ||||
|   | ||||
| @@ -17,6 +17,10 @@ class ResumeResource extends JsonResource | ||||
|             "user" => new SimpleUserResource($this->user), | ||||
|             "name" => $this->name, | ||||
|             "description" => $this->description, | ||||
|             "educationCount" => $this->education->count(), | ||||
|             "languageCount" => $this->languages->count(), | ||||
|             "technologyCount" => $this->technologies->count(), | ||||
|             "projectCount" => $this->projects->count(), | ||||
|             "createdAt" => $this->created_at->toDisplayString(), | ||||
|             "updatedAt" => $this->updated_at->toDisplayString(), | ||||
|         ]; | ||||
|   | ||||
| @@ -17,7 +17,6 @@ class ResumeFactory extends Factory | ||||
|     { | ||||
|         return [ | ||||
|             "name" => fn(array $attr) => empty($attr["user_id"]) ? $this->faker->name : null, | ||||
|             "description" => $this->faker->boolean(30) ? $this->faker->sentence : null, | ||||
|             "education" => $this->generateEducation(), | ||||
|             "languages" => $this->generateLanguages(), | ||||
|             "technologies" => $this->generateTechnologies(), | ||||
|   | ||||
| @@ -14,7 +14,6 @@ return new class() extends Migration { | ||||
|             $table->id(); | ||||
|             $table->foreignIdFor(User::class)->nullable()->constrained()->cascadeOnDelete(); | ||||
|             $table->string("name")->nullable(); | ||||
|             $table->string("description")->nullable(); | ||||
|             $table->json("education"); | ||||
|             $table->json("languages"); | ||||
|             $table->json("technologies"); | ||||
|   | ||||
| @@ -263,11 +263,25 @@ | ||||
|           <template #form="{ element, index }"> | ||||
|             <div class="gap-4 md:grid md:grid-cols-2 "> | ||||
|               <div class="py-4"> | ||||
|                 <Combobox | ||||
|                   v-model="element.name" | ||||
|                   label="Język" | ||||
|                   :items="languages" | ||||
|                 /> | ||||
|                 <label | ||||
|                   :for="`language-${index}-level`" | ||||
|                   class="block text-sm font-medium text-gray-700" | ||||
|                 > | ||||
|                   Język | ||||
|                 </label> | ||||
|                 <div class="mt-2"> | ||||
|                   <Combobox | ||||
|                     :id="`language-${index}-level`" | ||||
|                     v-model="element.name" | ||||
|                     :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 class="py-4"> | ||||
|                 <label | ||||
| @@ -313,11 +327,25 @@ | ||||
|           <template #form="{ element, index }"> | ||||
|             <div class="gap-4 md:grid md:grid-cols-2 "> | ||||
|               <div class="py-4"> | ||||
|                 <Combobox | ||||
|                   v-model="element.name" | ||||
|                   label="Technologia" | ||||
|                   :items="technologies" | ||||
|                 /> | ||||
|                 <label | ||||
|                   :for="`technology-${index}-level`" | ||||
|                   class="block text-sm font-medium text-gray-700" | ||||
|                 > | ||||
|                   Technologia | ||||
|                 </label> | ||||
|                 <div class="mt-2"> | ||||
|                   <Combobox | ||||
|                     :id="`technology-${index}-level`" | ||||
|                     v-model="element.name" | ||||
|                     :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 class="py-4"> | ||||
|                 <label | ||||
| @@ -367,6 +395,7 @@ | ||||
|                 <textarea | ||||
|                   :id="`project-description-${index}`" | ||||
|                   v-model="element.description" | ||||
|                   rows="5" | ||||
|                   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`] }" | ||||
|                 /> | ||||
| @@ -386,13 +415,11 @@ | ||||
|                 Technologie | ||||
|               </label> | ||||
|               <div class="mt-1 sm:mt-0"> | ||||
|                 <input | ||||
|                 <MultipleCombobox | ||||
|                   :id="`project-technologies-${index}`" | ||||
|                   v-model="element.technologies" | ||||
|                   type="text" | ||||
|                   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`] }" | ||||
|                 > | ||||
|                   :items="technologies" | ||||
|                 /> | ||||
|                 <p | ||||
|                   v-if="form.errors[`projects.${index}.technologies`]" | ||||
|                   class="mt-2 text-sm text-red-600" | ||||
| @@ -458,6 +485,7 @@ | ||||
|                 <textarea | ||||
|                   :id="`project-tasks-${index}`" | ||||
|                   v-model="element.tasks" | ||||
|                   rows="5" | ||||
|                   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`] }" | ||||
|                 /> | ||||
| @@ -495,10 +523,12 @@ | ||||
| <script setup> | ||||
| import { Listbox, ListboxOption, ListboxOptions, ListboxLabel, ListboxButton } from '@headlessui/vue' | ||||
| 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 DynamicSection from '@/Shared/Forms/DynamicSection' | ||||
| import Combobox from '@/Shared/Forms/Combobox' | ||||
| import MultipleCombobox from '@/Shared/Forms/MultipleCombobox' | ||||
| import LevelPicker from '@/Shared/Forms/LevelPicker' | ||||
| import useLevels from '@/Composables/useLevels' | ||||
|  | ||||
| @@ -527,7 +557,7 @@ const form = useForm({ | ||||
| function addProject() { | ||||
|   form.projects.push({ | ||||
|     description: null, | ||||
|     technologies: null, | ||||
|     technologies: [], | ||||
|     tasks: null, | ||||
|     startDate: null, | ||||
|     endDate: null, | ||||
|   | ||||
| @@ -263,17 +263,25 @@ | ||||
|           <template #form="{ element, index }"> | ||||
|             <div class="gap-4 md:grid md:grid-cols-2 "> | ||||
|               <div class="py-4"> | ||||
|                 <Combobox | ||||
|                   v-model="element.name" | ||||
|                   label="Język" | ||||
|                   :items="languages" | ||||
|                 /> | ||||
|                 <p | ||||
|                   v-if="form.errors[`languages.${index}.name`]" | ||||
|                   class="mt-2 text-sm text-red-600" | ||||
|                 <label | ||||
|                   :for="`language-${index}-level`" | ||||
|                   class="block text-sm font-medium text-gray-700" | ||||
|                 > | ||||
|                   {{ form.errors[`languages.${index}.name`] }} | ||||
|                 </p> | ||||
|                   Język | ||||
|                 </label> | ||||
|                 <div class="mt-2"> | ||||
|                   <Combobox | ||||
|                     :id="`language-${index}-level`" | ||||
|                     v-model="element.name" | ||||
|                     :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 class="py-4"> | ||||
|                 <label | ||||
| @@ -319,17 +327,25 @@ | ||||
|           <template #form="{ element, index }"> | ||||
|             <div class="gap-4 md:grid md:grid-cols-2 "> | ||||
|               <div class="py-4"> | ||||
|                 <Combobox | ||||
|                   v-model="element.name" | ||||
|                   label="Technologia" | ||||
|                   :items="technologies" | ||||
|                 /> | ||||
|                 <p | ||||
|                   v-if="form.errors[`technologies.${index}.name`]" | ||||
|                   class="mt-2 text-sm text-red-600" | ||||
|                 <label | ||||
|                   :for="`technology-${index}-level`" | ||||
|                   class="block text-sm font-medium text-gray-700" | ||||
|                 > | ||||
|                   {{ form.errors[`technologies.${index}.name`] }} | ||||
|                 </p> | ||||
|                   Technologia | ||||
|                 </label> | ||||
|                 <div class="mt-2"> | ||||
|                   <Combobox | ||||
|                     :id="`technology-${index}-level`" | ||||
|                     v-model="element.name" | ||||
|                     :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 class="py-4"> | ||||
|                 <label | ||||
| @@ -379,6 +395,7 @@ | ||||
|                 <textarea | ||||
|                   :id="`project-description-${index}`" | ||||
|                   v-model="element.description" | ||||
|                   rows="5" | ||||
|                   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`] }" | ||||
|                 /> | ||||
| @@ -398,13 +415,11 @@ | ||||
|                 Technologie | ||||
|               </label> | ||||
|               <div class="mt-1 sm:mt-0"> | ||||
|                 <input | ||||
|                 <MultipleCombobox | ||||
|                   :id="`project-technologies-${index}`" | ||||
|                   v-model="element.technologies" | ||||
|                   type="text" | ||||
|                   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`] }" | ||||
|                 > | ||||
|                   :items="technologies" | ||||
|                 /> | ||||
|                 <p | ||||
|                   v-if="form.errors[`projects.${index}.technologies`]" | ||||
|                   class="mt-2 text-sm text-red-600" | ||||
| @@ -470,7 +485,7 @@ | ||||
|                 <textarea | ||||
|                   :id="`project-tasks-${index}`" | ||||
|                   v-model="element.tasks" | ||||
|                   rows="3" | ||||
|                   rows="5" | ||||
|                   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`] }" | ||||
|                 /> | ||||
| @@ -513,6 +528,7 @@ import { useForm } from '@inertiajs/inertia-vue3' | ||||
| import FlatPickr from 'vue-flatpickr-component' | ||||
| import DynamicSection from '@/Shared/Forms/DynamicSection' | ||||
| import Combobox from '@/Shared/Forms/Combobox' | ||||
| import MultipleCombobox from '@/Shared/Forms/MultipleCombobox' | ||||
| import LevelPicker from '@/Shared/Forms/LevelPicker' | ||||
| import useLevels from '@/Composables/useLevels' | ||||
|  | ||||
| @@ -548,7 +564,7 @@ const form = useForm({ | ||||
| function addProject() { | ||||
|   form.projects.push({ | ||||
|     description: null, | ||||
|     technologies: null, | ||||
|     technologies: [], | ||||
|     tasks: null, | ||||
|     startDate: null, | ||||
|     endDate: null, | ||||
|   | ||||
| @@ -25,12 +25,6 @@ | ||||
|               > | ||||
|                 Użytkownik | ||||
|               </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 | ||||
|                 scope="col" | ||||
|                 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 | ||||
|               </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 | ||||
|                 scope="col" | ||||
|                 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> | ||||
|                 <template v-else> | ||||
|                   {{ resume.name }} | ||||
|                   <span class="text-sm font-medium text-gray-900 break-all">{{ resume.name }}</span> | ||||
|                 </template> | ||||
|               </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"> | ||||
|                 {{ resume.createdAt }} | ||||
|               </td> | ||||
|               <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> | ||||
|                 {{ resume.updatedAt }} | ||||
|               </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"> | ||||
|                 <Menu | ||||
|                   as="div" | ||||
|   | ||||
| @@ -3,11 +3,9 @@ | ||||
|     as="div" | ||||
|     nullable | ||||
|   > | ||||
|     <ComboboxLabel class="block text-sm font-medium text-gray-700"> | ||||
|       {{ label }} | ||||
|     </ComboboxLabel> | ||||
|     <div class="relative mt-2"> | ||||
|     <div class="relative"> | ||||
|       <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" | ||||
|         @change="query = $event.target.value" | ||||
|       /> | ||||
| @@ -51,14 +49,13 @@ import { | ||||
|   Combobox, | ||||
|   ComboboxButton, | ||||
|   ComboboxInput, | ||||
|   ComboboxLabel, | ||||
|   ComboboxOption, | ||||
|   ComboboxOptions, | ||||
| } from '@headlessui/vue' | ||||
|  | ||||
| const props = defineProps({ | ||||
|   label: null, | ||||
|   items: Array, | ||||
|   id: String, | ||||
| }) | ||||
|  | ||||
| const query = ref('') | ||||
|   | ||||
| @@ -23,7 +23,6 @@ | ||||
|           </button> | ||||
|           <Disclosure | ||||
|             v-slot="{ open }" | ||||
|             :default-open="false" | ||||
|             as="div" | ||||
|             class="flex-1 border border-gray-200" | ||||
|           > | ||||
|   | ||||
| @@ -1,21 +1,27 @@ | ||||
| <template> | ||||
|   <Combobox | ||||
|     v-model="selectedItems" | ||||
|     as="div" | ||||
|     nullable | ||||
|     multiple | ||||
|   > | ||||
|     <ComboboxLabel class="block text-sm font-medium text-gray-700"> | ||||
|       {{ label }} | ||||
|     </ComboboxLabel> | ||||
|     <div class="flex flex-wrap gap-3"> | ||||
|       <button | ||||
|         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"> | ||||
|       <ComboboxInput | ||||
|         as="template" | ||||
|         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" | ||||
|         :display-value="(item) => item" | ||||
|         :id="id" | ||||
|         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" | ||||
|         @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"> | ||||
|         <SelectorIcon class="h-5 w-5 text-gray-400" /> | ||||
|       </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']"> | ||||
|             <span :class="['block truncate', selected && 'font-semibold']"> | ||||
|               {{ item.name }} | ||||
|               {{ item }} | ||||
|             </span> | ||||
|  | ||||
|             <span | ||||
| @@ -56,21 +62,31 @@ import { | ||||
|   Combobox, | ||||
|   ComboboxButton, | ||||
|   ComboboxInput, | ||||
|   ComboboxLabel, | ||||
|   ComboboxOption, | ||||
|   ComboboxOptions, | ||||
| } from '@headlessui/vue' | ||||
|  | ||||
| const props = defineProps({ | ||||
|   label: null, | ||||
|   items: Array, | ||||
|   modelValue: Array, | ||||
|   id: String, | ||||
| }) | ||||
|  | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
|  | ||||
| const query = ref('') | ||||
|  | ||||
| const selectedItems = computed({ | ||||
|   get: () => props.modelValue, | ||||
|   set: (value) => { | ||||
|     query.value = '' | ||||
|     emit('update:modelValue', value) | ||||
|   }, | ||||
| }) | ||||
|  | ||||
| const filteredItems = computed(() => | ||||
|   query.value === '' | ||||
|     ? props.items | ||||
|     : props.items.filter((item) => item.name.toLowerCase().includes(query.value.toLowerCase())), | ||||
|     : props.items.filter((item) => item.toLowerCase().includes(query.value.toLowerCase())), | ||||
| ) | ||||
| </script> | ||||
		Reference in New Issue
	
	Block a user