Rework frontend

This commit is contained in:
F4ilji
2025-03-23 00:38:16 +05:00
parent 17518ceed2
commit 49072e50c7
438 changed files with 14375 additions and 20015 deletions
@@ -0,0 +1,49 @@
<template>
<div class="">
<img @click="toggler = !toggler" class="shrink-0 w-full md:w-[200px] md:h-[300px] rounded-xl object-cover" :src="'/storage/' + photo" alt="Avatar">
</div>
<FsLightbox class="" :toggler="toggler" :sources="[domainPath + '/storage/' + photo]"/>
</template>
<script>
import slugify from "slugify";
import FsLightbox from "fslightbox-vue";
export default {
name: "PersonAvatarBlock",
components: { FsLightbox },
data() {
return {
toggler: false,
domainPath: null,
}
},
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
},
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
photo: {
type: String,
},
},
}
</script>
<style>
.fslightbox-container {
margin: 0 !important;
}
</style>
@@ -0,0 +1,192 @@
<template>
<div class="flex justify-between pb-4 items-center">
<div class="flex w-full sm:items-center gap-x-5 sm:gap-x-3">
<div class="grow">
<div class="grid sm:flex sm:justify-between sm:items-center gap-2">
<BreadcrumbsWrapper v-if="breadcrumbs">
<li class="text-sm">
<Link :href="route('index')" class="flex items-center text-gray-500 hover:text-blue-600" href="/">
<BasicIcon class="size-5" name="home" />
</Link>
</li>
<li v-if="breadcrumbs.mainSection" class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue cursor-pointer" @click.prevent="handleSectionClick(breadcrumbs.mainSection)">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.mainSection.data.title, 25) }}
</span>
</li>
<li v-if="breadcrumbs.subSection" class="text-sm">
<span class="flex items-center text-gray-500 hover:text-primaryBlue cursor-pointer" @click.prevent="handleSubSectionClick(breadcrumbs.mainSection, breadcrumbs.subSection)">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ textLimit(breadcrumbs.subSection.data.title, 25) }}
</span>
</li>
<li class="text-sm">
<Link :href="$page.props.ziggy.location" class="flex items-center text-gray-500 hover:text-blue-600">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ personName }}
</Link>
</li>
</BreadcrumbsWrapper>
<BreadcrumbsWrapper v-else>
<li class="text-sm">
<Link :href="route('index')" class="flex items-center text-gray-500 hover:text-blue-600" href="/">
<BasicIcon class="size-5" name="home" />
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</Link>
</li>
<li class="text-sm">
<Link :href="$page.props.ziggy.location" class="flex items-center text-gray-500 hover:text-blue-600">
<svg class="flex-shrink-0 mx-2 overflow-visible h-2.5 w-2.5 text-gray-400"
width="16" height="16"
viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14"
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
{{ personName }}
</Link>
</li>
</BreadcrumbsWrapper>
</div>
</div>
</div>
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import slugify from "slugify";
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
import BreadcrumbsWrapper from "@/componentss/ui/wrappers/BreadcrumbsWrapper.vue";
export default {
name: "PersonBreadcrumbs",
components: {BreadcrumbsWrapper, BasicIcon, Link},
data() {
return {
}
},
methods: {
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText
LimitedText = text.substring(0, symbols)
return LimitedText + "..."
}
return text
},
isMobileDevice() {
if (typeof window !== 'undefined') {
return window.innerWidth < 1024; // Проверка на мобильные устройства
}
return false; // По умолчанию возвращаем false, когда не в браузере
},
handleSectionClick(breadcrumb) {
if (this.isMobileDevice()) {
this.toggleMobileNavSection(breadcrumb)
} else {
this.toggleDesktopNavSection(breadcrumb)
}
},
handleSubSectionClick(mainSectionBreadcrumb, breadcrumb) {
if (this.isMobileDevice()) {
this.toggleMobileNavSubSection(mainSectionBreadcrumb, breadcrumb)
} else {
this.toggleDesktopNavSubSection(mainSectionBreadcrumb, breadcrumb)
}
},
highlightNavItem(item) {
item.classList.add('animate-pulse');
setTimeout(() => {
item.classList.remove('animate-pulse');
}, 4000);
},
openMobileNavMenu() {
const openMobileNavBtn = document.getElementById('open-mobile-btn');
openMobileNavBtn.click();
},
toggleMobileNavSubSection(mainSectionBreadcrumb, breadcrumb) {
this.openMobileNavMenu()
const mobileNavElement = document.getElementById('open-mobile-nav');
const sectionNavBlock = mobileNavElement.querySelector('#nav-section-accordion-' + mainSectionBreadcrumb.data.slug);
const sectionNavBlockBtn = mobileNavElement.querySelector('#nav-section-accordion-btn-' + mainSectionBreadcrumb.data.slug);
if (!sectionNavBlock.classList.contains('active')) {
sectionNavBlockBtn.click()
}
const subSectionNavBlock = mobileNavElement.querySelector('#nav-sub-section-accordion-' + breadcrumb.data.slug);
const subSectionNavBlockBtn = mobileNavElement.querySelector('#nav-sub-section-accordion-btn-' + breadcrumb.data.slug);
if (subSectionNavBlock.classList.contains('active')) {
subSectionNavBlockBtn.click()
}
this.highlightNavItem(subSectionNavBlock)
},
toggleMobileNavSection(breadcrumb) {
const mobileNavElement = document.getElementById('open-mobile-nav');
const sectionNavBlock = mobileNavElement.querySelector('#nav-section-accordion-' + breadcrumb.data.slug);
const sectionNavBlockBtn = mobileNavElement.querySelector('#nav-section-accordion-btn-' + breadcrumb.data.slug);
if (sectionNavBlock.classList.contains('active')) {
sectionNavBlockBtn.click()
}
this.openMobileNavMenu()
this.highlightNavItem(sectionNavBlock)
},
toggleDesktopNavSubSection(mainSectionBreadcrumb, breadcrumb) {
const desktopNavElement = document.getElementById('desktop-nav');
const sectionNavTitle = desktopNavElement.querySelector('#nav-sub-section-title-' + breadcrumb.data.slug);
const sectionNavBlockBtn = desktopNavElement.querySelector('#nav-section-btn-' + mainSectionBreadcrumb.data.slug);
sectionNavBlockBtn.click()
this.highlightNavItem(sectionNavTitle)
},
toggleDesktopNavSection(breadcrumb) {
const desktopNavElement = document.getElementById('desktop-nav');
const sectionNavBlock = desktopNavElement.querySelector('#nav-section-menu-' + breadcrumb.data.slug);
const sectionNavBlockBtn = desktopNavElement.querySelector('#nav-section-btn-' + breadcrumb.data.slug);
sectionNavBlockBtn.click()
// this.highlightNavItem(sectionNavBlock)
},
},
props: {
breadcrumbs: {
type: Object,
default: () => ({}), // Default to an empty object
},
personName: {
type: String,
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,40 @@
<!-- NavLink.vue -->
<template>
<a :class="linkClass"
class="duration-150 block py-1 px-2 leading-[1.6] rounded-md"
:href="href">
{{ label }}
</a>
</template>
<script>
export default {
name: 'PersonNavLink',
props: {
label: {
type: String,
required: true
},
href: {
type: String,
required: true
},
isActive: {
type: Boolean,
default: false
}
},
computed: {
linkClass() {
return {
'translate-x-2 text-[#135aae]': this.isActive,
'bg-transperant text-gray-600 hover:text-gray-900': !this.isActive
};
}
}
}
</script>
<style scoped>
/* Добавьте стили, если это необходимо */
</style>
@@ -0,0 +1,126 @@
<!-- NavLink.vue -->
<template>
<div class="flex items-center gap-x-10 gap-y-4 flex-wrap">
<PersonAvatarBlock v-if="person.data.details.photo" :photo="person.data.details.photo" />
<div class="grow">
<h1 class="text-2xl font-medium text-gray-800 dark:text-neutral-200">
{{ person.data.name }}
</h1>
<div class="">
<template v-for="division_work in person.data.divisions_works">
<Link :href="route('client.division.show', division_work.slug)" class="text-sm text-blue-500 dark:text-neutral-400 mr-2">
{{ division_work.position }}
</Link>
</template>
<template v-for="faculty_work in person.data.faculties_works">
<Link :href="route('client.faculty.show', faculty_work.slug)" class="text-sm text-blue-500 dark:text-neutral-400 mr-2">
{{ faculty_work.position }}
</Link>
</template>
<template v-for="division_work in person.data.departments_work">
<Link :href="route('client.department.show', { facultySlug: division_work.faculty_slug, departmentSlug: division_work.slug })" class="text-sm text-blue-500 dark:text-neutral-400 mr-2">
{{ division_work.position }}
</Link>
</template>
<template v-for="division_tech in person.data.departments_teach">
<Link :href="route('client.department.show', { facultySlug: division_tech.faculty_slug, departmentSlug: division_tech.slug })" class="text-sm text-blue-500 dark:text-neutral-400 mr-2">
{{ division_tech.position }}
</Link>
</template>
</div>
<ul class="mt-5 flex flex-col gap-y-3">
<li v-if="person.data.details.contactPhone" class="flex items-center gap-x-2.5">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="shrink-0 size-3.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 0 1-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 0 0-1.091-.852H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z" />
</svg>
<a class="text-[13px] text-gray-500 underline hover:text-gray-800 hover:decoration-2 focus:outline-none focus:decoration-2 dark:text-neutral-500 dark:hover:text-neutral-400" href="#">
{{ person.data.details.contactPhone }}
</a>
</li>
<li v-if="person.data.details.contactEmail" class="flex items-center gap-x-2.5">
<svg class="shrink-0 size-3.5" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
</svg>
<a class="text-[13px] text-gray-500 underline hover:text-gray-800 hover:decoration-2 focus:outline-none focus:decoration-2 dark:text-neutral-500 dark:hover:text-neutral-400" href="#">
{{ person.data.details.contactEmail }}
</a>
</li>
</ul>
</div>
</div>
<div class="mt-5 sm:mt-7">
<EducationBlock v-if="person.data.details.education.length > 0" title="Направление подготовки" :educations="person.data.details.education" />
</div>
<div class="mt-8">
<ListBlock v-if="person.data.details.awards.length > 0" name-list="Награды" :list="person.data.details.awards" />
</div>
<div class="mt-8">
<ListBlock v-if="person.data.details.professionalRetraining.length > 0" name-list="Профессиональная переподготовка" :list="person.data.details.professionalRetraining" />
</div>
<div class="mt-8">
<ListBlock v-if="person.data.details.professionalDevelopment.length > 0" name-list="Повышение квалификации" :list="person.data.details.professionalDevelopment" />
</div>
<div class="mt-8">
<ListBlock v-if="person.data.details.professDisciplines.length > 0" name-list="Преподаваемые дисциплины" :list="person.data.details.professDisciplines" />
</div>
<div class="mt-8">
<WorkExperienceBlock v-if="person.data.details.workExperience.total" title="Стаж" :work-exp-by-prof="person.data.details.workExperience.byProf" :work-exp-total="person.data.details.workExperience.total" />
</div>
<div class="mt-8">
<ListBlock v-if="person.data.details.attendedConferences.length > 0" name-list="Участие в конференциях" :list="person.data.details.attendedConferences" />
</div>
<div class="mt-8">
<PublicationBlock v-if="person.data.details.publications.length > 0" name-list="Публикации" :list="person.data.details.publications" />
</div>
<div class="mt-8">
<OtherBlock v-if="person.data.details.other?.length > 0" name-list="Другое" :blocks="person.data.details.other" />
</div>
</template>
<script>
import {Link} from "@inertiajs/vue3";
import OtherBlock from "@/componentss/features/persons/components/sections/blocks/OtherBlock.vue";
import EducationBlock from "@/componentss/features/persons/components/sections/blocks/EducationBlock.vue";
import ListBlock from "@/componentss/features/persons/components/sections/blocks/ListBlock.vue";
import PersonAvatarBlock from "@/componentss/features/persons/components/PersonAvatarBlock.vue";
import WorkExperienceBlock from "@/componentss/features/persons/components/sections/blocks/WorkExperienceBlock.vue";
import PublicationBlock from "@/componentss/features/persons/components/sections/blocks/PublicationBlock.vue";
export default {
name: 'InfoSection',
components: {OtherBlock, EducationBlock, ListBlock, PersonAvatarBlock, Link, WorkExperienceBlock, PublicationBlock},
props: {
person: {
type: Object,
required: true
},
},
computed: {
linkClass() {
return {
'translate-x-2 text-[#135aae]': this.isActive,
'bg-transperant text-gray-600 hover:text-gray-900': !this.isActive
};
}
}
}
</script>
<style scoped>
/* Добавьте стили, если это необходимо */
</style>
@@ -0,0 +1,60 @@
<!-- NavLink.vue -->
<template>
<div>
<h2 :id="generateSlug(title)" class="mb-3 font-medium text-gray-800 dark:text-neutral-200">
{{ title }}
</h2>
<template v-for="education in educations">
<div class="grid grid-cols-1 sm:grid-cols-1 my-4">
<div class="p-4 border border-gray-200 rounded-lg dark:border-neutral-700">
<h3 class="mb-1 text-xs text-gray-600 dark:text-neutral-400">
{{ education.year }}
</h3>
<p class="font-semibold text-sm text-gray-800 dark:text-neutral-200">
{{ education.content }}
</p>
<p class="mt-1 text-sm text-gray-600 dark:text-neutral-400">
{{ education.institution }}
</p>
</div>
</div>
</template>
</div>
</template>
<script>
import slugify from "slugify";
export default {
name: 'EducationBlock',
props: {
educations: {
type: Object,
required: true
},
title: {
type: String,
required: true
}
},
methods: {
generateSlug(text) {
return slugify(text, {
lower: true,
strict: true,
locale: "ru",
});
},
}
}
</script>
<style scoped>
/* Добавьте стили, если это необходимо */
</style>
@@ -0,0 +1,46 @@
<!-- NavLink.vue -->
<template>
<div>
<h2 :id="generateSlug(nameList)" class="mb-3 font-medium text-gray-800 dark:text-neutral-200">
{{ nameList }}
</h2>
<ul class="list-disc ms-6 mt-3 space-y-1.5">
<template v-for="el in list">
<li class="ps-1 text-sm text-gray-600 dark:text-neutral-400">
{{ el.item }}
</li>
</template>
</ul>
</div>
</template>
<script>
import slugify from "slugify";
export default {
name: 'ListBlock',
props: {
list: {
type: String,
required: true
},
nameList: {
type: String,
required: true,
},
},
methods: {
generateSlug(text) {
return slugify(text, {
lower: true,
strict: true,
locale: "ru",
});
},
}
}
</script>
<style scoped>
/* Добавьте стили, если это необходимо */
</style>
@@ -0,0 +1,42 @@
<!-- NavLink.vue -->
<template>
<div>
<h2 :id="generateSlug(nameList)" class="mb-3 font-medium text-gray-800 dark:text-neutral-200">
{{ nameList }}
</h2>
<Builder :blocks="blocks" />
</div>
</template>
<script>
import slugify from "slugify";
import Builder from "@/componentss/shared/builder/pageBuilder/Builder.vue";
export default {
name: 'OtherBlock',
components: {Builder},
props: {
blocks: {
type: String,
required: true
},
nameList: {
type: String,
required: true,
},
},
methods: {
generateSlug(text) {
return slugify(text, {
lower: true,
strict: true,
locale: "ru",
});
},
}
}
</script>
<style scoped>
/* Добавьте стили, если это необходимо */
</style>
@@ -0,0 +1,48 @@
<!-- NavLink.vue -->
<template>
<div>
<template v-for="el in list">
<h2 :id="generateSlug(el.category_publication)" class="my-4 font-medium text-gray-800">{{ el.category_publication }}</h2>
<ul class="list-disc ms-6 mt-3 space-y-1.5">
<template v-for="el in el.publication">
<li class="ps-1 text-sm text-gray-600 dark:text-neutral-400">
{{ el.item }}
</li>
</template>
</ul>
</template>
</div>
</template>
<script>
import slugify from "slugify";
export default {
name: 'PublicationBlock',
props: {
list: {
type: String,
required: true
},
nameList: {
type: String,
required: true,
},
},
methods: {
generateSlug(text) {
return slugify(text, {
lower: true,
strict: true,
locale: "ru",
});
},
}
}
</script>
<style scoped>
/* Добавьте стили, если это необходимо */
</style>
@@ -0,0 +1,49 @@
<!-- NavLink.vue -->
<template>
<h2 :id="generateSlug(title)" class="mb-3 font-medium text-gray-800 dark:text-neutral-200">
{{ title }}
</h2>
<div class="grid grid-cols-1 sm:grid-cols-1 gap-3">
<p class="mt-1 text-sm text-gray-600 dark:text-neutral-400">
Стаж работы: {{ workExpTotal }}
</p>
<p class="mt-1 text-sm text-gray-600 dark:text-neutral-400">
Стаж по специальности: {{ workExpByProf }}
</p>
</div>
</template>
<script>
import slugify from "slugify";
export default {
name: 'WorkExperienceBlock',
props: {
workExpTotal: {
type: String,
required: true
},
workExpByProf: {
type: String,
required: true
},
title: {
type: String,
required: true
},
},
methods: {
generateSlug(text) {
return slugify(text, {
lower: true,
strict: true,
locale: "ru",
});
},
}
}
</script>
<style scoped>
/* Добавьте стили, если это необходимо */
</style>