Структура БД + Фронт
1. Обновил структуру БД 2. Сделал умное поведение календаря и выбора исполнителя
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Transition name="dropdown">
|
<Transition name="dropdown">
|
||||||
<div v-if="isOpen" class="calendar">
|
<div v-if="isOpen" class="calendar" :class="{ 'open-up': openUp }" ref="calendarRef">
|
||||||
<div class="calendar-header">
|
<div class="calendar-header">
|
||||||
<button class="nav-btn" @click="prevMonth">
|
<button class="nav-btn" @click="prevMonth">
|
||||||
<i data-lucide="chevron-left"></i>
|
<i data-lucide="chevron-left"></i>
|
||||||
@@ -41,9 +41,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="calendar-footer">
|
|
||||||
<button class="today-btn" @click="selectToday">Сегодня</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,7 +56,9 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const datepickerRef = ref(null)
|
const datepickerRef = ref(null)
|
||||||
|
const calendarRef = ref(null)
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
|
const openUp = ref(false)
|
||||||
const currentMonth = ref(new Date().getMonth())
|
const currentMonth = ref(new Date().getMonth())
|
||||||
const currentYear = ref(new Date().getFullYear())
|
const currentYear = ref(new Date().getFullYear())
|
||||||
|
|
||||||
@@ -136,7 +135,7 @@ const calendarDays = computed(() => {
|
|||||||
return days
|
return days
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggleCalendar = () => {
|
const toggleCalendar = async () => {
|
||||||
isOpen.value = !isOpen.value
|
isOpen.value = !isOpen.value
|
||||||
if (isOpen.value) {
|
if (isOpen.value) {
|
||||||
// Устанавливаем текущий месяц на выбранную дату или сегодня
|
// Устанавливаем текущий месяц на выбранную дату или сегодня
|
||||||
@@ -148,9 +147,11 @@ const toggleCalendar = () => {
|
|||||||
currentMonth.value = new Date().getMonth()
|
currentMonth.value = new Date().getMonth()
|
||||||
currentYear.value = new Date().getFullYear()
|
currentYear.value = new Date().getFullYear()
|
||||||
}
|
}
|
||||||
nextTick(() => {
|
|
||||||
|
await nextTick()
|
||||||
|
updatePosition()
|
||||||
|
|
||||||
if (window.lucide) window.lucide.createIcons()
|
if (window.lucide) window.lucide.createIcons()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,13 +201,38 @@ const handleClickOutside = (e) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Пересчёт позиции при изменении размера/скролле
|
||||||
|
const updatePosition = () => {
|
||||||
|
if (isOpen.value && datepickerRef.value) {
|
||||||
|
const rect = datepickerRef.value.getBoundingClientRect()
|
||||||
|
const calendarHeight = 350
|
||||||
|
const footerOffset = 80 // отступ для футера панели с кнопками
|
||||||
|
|
||||||
|
// Ищем родительский контейнер со скроллом (SlidePanel)
|
||||||
|
const scrollParent = datepickerRef.value.closest('.panel-body')
|
||||||
|
let availableBottom = window.innerHeight - footerOffset
|
||||||
|
|
||||||
|
if (scrollParent) {
|
||||||
|
const parentRect = scrollParent.getBoundingClientRect()
|
||||||
|
availableBottom = parentRect.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
const spaceBelow = availableBottom - rect.bottom
|
||||||
|
openUp.value = spaceBelow < calendarHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('click', handleClickOutside)
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
window.addEventListener('resize', updatePosition)
|
||||||
|
window.addEventListener('scroll', updatePosition, true)
|
||||||
if (window.lucide) window.lucide.createIcons()
|
if (window.lucide) window.lucide.createIcons()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('click', handleClickOutside)
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
window.removeEventListener('resize', updatePosition)
|
||||||
|
window.removeEventListener('scroll', updatePosition, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(isOpen, () => {
|
watch(isOpen, () => {
|
||||||
@@ -278,7 +304,7 @@ watch(isOpen, () => {
|
|||||||
|
|
||||||
.calendar {
|
.calendar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: calc(100% + 8px);
|
top: calc(100% + 8px);
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: #1e1e24;
|
background: #1e1e24;
|
||||||
@@ -286,6 +312,12 @@ watch(isOpen, () => {
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar.open-up {
|
||||||
|
top: auto;
|
||||||
|
bottom: calc(100% + 8px);
|
||||||
box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,29 +417,6 @@ watch(isOpen, () => {
|
|||||||
background: #00e6b8;
|
background: #00e6b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-footer {
|
|
||||||
margin-top: 12px;
|
|
||||||
padding-top: 12px;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.today-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--accent);
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
transition: all 0.15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.today-btn:hover {
|
|
||||||
background: var(--accent-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Transition */
|
/* Transition */
|
||||||
.dropdown-enter-active,
|
.dropdown-enter-active,
|
||||||
@@ -418,6 +427,11 @@ watch(isOpen, () => {
|
|||||||
.dropdown-enter-from,
|
.dropdown-enter-from,
|
||||||
.dropdown-leave-to {
|
.dropdown-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar.open-up.dropdown-enter-from,
|
||||||
|
.calendar.open-up.dropdown-leave-to {
|
||||||
transform: translateY(8px);
|
transform: translateY(8px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<!-- Выпадающий список -->
|
<!-- Выпадающий список -->
|
||||||
<Transition name="dropdown">
|
<Transition name="dropdown">
|
||||||
<div v-if="isOpen" class="dropdown-menu">
|
<div v-if="isOpen" class="dropdown-menu" :class="{ 'open-up': openUp }">
|
||||||
<input
|
<input
|
||||||
v-if="searchable"
|
v-if="searchable"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -115,6 +115,7 @@ const emit = defineEmits(['update:modelValue', 'change'])
|
|||||||
const dropdownRef = ref(null)
|
const dropdownRef = ref(null)
|
||||||
const searchInputRef = ref(null)
|
const searchInputRef = ref(null)
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
|
const openUp = ref(false)
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
|
||||||
// Выбранная опция
|
// Выбранная опция
|
||||||
@@ -138,6 +139,7 @@ const toggleDropdown = async () => {
|
|||||||
isOpen.value = !isOpen.value
|
isOpen.value = !isOpen.value
|
||||||
if (isOpen.value) {
|
if (isOpen.value) {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
updatePosition()
|
||||||
searchInputRef.value?.focus()
|
searchInputRef.value?.focus()
|
||||||
refreshIcons()
|
refreshIcons()
|
||||||
}
|
}
|
||||||
@@ -159,6 +161,27 @@ const handleClickOutside = (e) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Пересчёт позиции при изменении размера/скролле
|
||||||
|
const updatePosition = () => {
|
||||||
|
if (isOpen.value && dropdownRef.value) {
|
||||||
|
const rect = dropdownRef.value.getBoundingClientRect()
|
||||||
|
const menuHeight = 300
|
||||||
|
const footerOffset = 80 // отступ для футера панели с кнопками
|
||||||
|
|
||||||
|
// Ищем родительский контейнер со скроллом (SlidePanel)
|
||||||
|
const scrollParent = dropdownRef.value.closest('.panel-body')
|
||||||
|
let availableBottom = window.innerHeight - footerOffset
|
||||||
|
|
||||||
|
if (scrollParent) {
|
||||||
|
const parentRect = scrollParent.getBoundingClientRect()
|
||||||
|
availableBottom = parentRect.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
const spaceBelow = availableBottom - rect.bottom
|
||||||
|
openUp.value = spaceBelow < menuHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Обновление иконок
|
// Обновление иконок
|
||||||
const refreshIcons = () => {
|
const refreshIcons = () => {
|
||||||
if (window.lucide) {
|
if (window.lucide) {
|
||||||
@@ -168,11 +191,15 @@ const refreshIcons = () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('click', handleClickOutside)
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
window.addEventListener('resize', updatePosition)
|
||||||
|
window.addEventListener('scroll', updatePosition, true)
|
||||||
refreshIcons()
|
refreshIcons()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('click', handleClickOutside)
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
window.removeEventListener('resize', updatePosition)
|
||||||
|
window.removeEventListener('scroll', updatePosition, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUpdated(refreshIcons)
|
onUpdated(refreshIcons)
|
||||||
@@ -233,6 +260,12 @@ onUpdated(refreshIcons)
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu.open-up {
|
||||||
|
top: auto;
|
||||||
|
bottom: calc(100% + 6px);
|
||||||
|
box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-search {
|
.dropdown-search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
@@ -366,4 +399,9 @@ onUpdated(refreshIcons)
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-8px);
|
transform: translateY(-8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu.open-up.dropdown-enter-from,
|
||||||
|
.dropdown-menu.open-up.dropdown-leave-to {
|
||||||
|
transform: translateY(8px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
Target Server Version : 90200 (9.2.0)
|
Target Server Version : 90200 (9.2.0)
|
||||||
File Encoding : 65001
|
File Encoding : 65001
|
||||||
|
|
||||||
Date: 12/01/2026 01:11:18
|
Date: 13/01/2026 08:18:18
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SET NAMES utf8mb4;
|
SET NAMES utf8mb4;
|
||||||
@@ -45,7 +45,7 @@ CREATE TABLE `accounts_session` (
|
|||||||
`ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
`ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||||
`user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
`user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for cards_task
|
-- Table structure for cards_task
|
||||||
@@ -58,6 +58,7 @@ CREATE TABLE `cards_task` (
|
|||||||
`id_account` int NULL DEFAULT NULL,
|
`id_account` int NULL DEFAULT NULL,
|
||||||
`order` int NULL DEFAULT NULL,
|
`order` int NULL DEFAULT NULL,
|
||||||
`column_id` int NULL DEFAULT NULL,
|
`column_id` int NULL DEFAULT NULL,
|
||||||
|
`archive` tinyint NULL DEFAULT NULL,
|
||||||
`date` datetime NULL DEFAULT NULL,
|
`date` datetime NULL DEFAULT NULL,
|
||||||
`date_create` datetime NULL DEFAULT NULL,
|
`date_create` datetime NULL DEFAULT NULL,
|
||||||
`file_img` json NULL,
|
`file_img` json NULL,
|
||||||
@@ -65,7 +66,7 @@ CREATE TABLE `cards_task` (
|
|||||||
`descript` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
`descript` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||||
`descript_full` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
`descript_full` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for columns
|
-- Table structure for columns
|
||||||
@@ -76,7 +77,7 @@ CREATE TABLE `columns` (
|
|||||||
`name_columns` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
`name_columns` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||||
`color` varchar(7) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
`color` varchar(7) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 56 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for departments
|
-- Table structure for departments
|
||||||
|
|||||||
Reference in New Issue
Block a user