Files
ntspi-app/resources/js/mixins/Helpers.js
T
2026-04-07 17:54:26 +05:00

339 lines
13 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import slugify from "slugify";
export const helpers = {
methods: {
HAS_ACTIVE_PAGE(section) {
if (!this.$page?.props?.ziggy) return false;
if (section.pages) {
return section.pages.some(page => this.IS_SAME_ROUTE(page.path));
}
if (section.subSections) {
return section.subSections.some(subSection => this.HAS_ACTIVE_PAGE(subSection));
}
return false;
},
IS_SAME_ROUTE(route) {
if (!this.$page?.props?.ziggy) return false;
const currentLocation = this.$page.props.ziggy.location;
if (!currentLocation) return false;
if (route === currentLocation) {
return true;
}
try {
const currentUrl = this.$page.props.ziggy.url + '/' + route;
return currentLocation === currentUrl;
} catch {
return false;
}
},
TEXT_LIMIT(text, symbols) {
if (!text) return '';
if (typeof text !== 'string') return String(text);
return text.length > symbols
? text.substring(0, symbols) + "..."
: text;
},
GENERATE_SLUG(text) {
if (typeof text !== 'string') return '';
try {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
} catch {
return text.toLowerCase().replace(/\s+/g, '-');
}
},
GET_BASE_URL() {
if (typeof window === 'undefined') {
return import.meta.env.VITE_APP_URL || '/';
}
return window.location.origin + '/';
},
GET_BASE_STORAGE_URL() {
if (typeof window === 'undefined') {
return (import.meta.env.VITE_APP_URL || '/') + '/storage/';
}
return window.location.origin + '/storage/';
},
SET_DOCUMENT_TITLE(title, subtitle = 'Dashboard') {
if (typeof window === 'undefined') return;
if (title) {
document.title = `${title} | ${subtitle}`;
} else {
document.title = subtitle;
}
},
// ==========================================
// НОВЫЕ МЕТОДЫ (добавлены после анализа)
// ==========================================
/**
* Форматирование даты с различными опциями
* @param {string|Date} date - Дата для форматирования
* @param {string} format - 'full' (с временем) или 'short' (без времени)
* @returns {string} Отформатированная дата или '—'
*/
FORMAT_DATE(date, format = 'full') {
if (!date) return '—';
const options = format === 'short'
? { day: 'numeric', month: 'short', year: 'numeric' }
: { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' };
return new Date(date).toLocaleDateString('ru-RU', options);
},
/**
* Форматирование размера файла в читаемый вид
* @param {number} bytes - Размер в байтах
* @returns {string} Отформатированный размер (Bytes, KB, MB, GB)
*/
FORMAT_FILE_SIZE(bytes) {
if (!bytes) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
},
/**
* CSS классы для бейджа статуса (активен/неактивен)
* @param {boolean} isActive - Статус активности
* @returns {string} Tailwind CSS классы для бейджа
*/
STATUS_BADGE_CLASS(isActive) {
const base = 'inline-flex items-center px-2.5 py-1 rounded-md text-xs font-medium border';
return isActive
? `${base} bg-success/10 text-success border-success/20`
: `${base} bg-danger/10 text-danger border-danger/20`;
},
/**
* Текстовая метка для статуса публикации
* @param {string} status - Статус (verification, published, rejected)
* @returns {string} Текстовая метка статуса
*/
STATUS_LABEL(status) {
const labels = {
verification: 'На модерации',
published: 'Опубликовано',
rejected: 'Отклонено',
};
return labels[status] || status || '—';
},
/**
* Разрешение URL для изображений (строка или File объект)
* @param {string|File} source - URL/File объект
* @returns {string|null} Разрешённый URL или null
*/
RESOLVE_ASSET_URL(source) {
if (!source) return null;
if (typeof source === 'string') {
// Data URL (base64) — возвращаем как есть
if (source.startsWith('data:')) return source;
// Absolute URL — возвращаем как есть
if (source.startsWith('http')) return source;
// Relative path — добавляем базовый URL хранилища
return this.GET_BASE_STORAGE_URL() + source;
}
// File или Blob объект
if (source instanceof File || source instanceof Blob) {
return URL.createObjectURL(source);
}
// Неизвестный тип — логируем и возвращаем null
console.warn('[RESOLVE_ASSET_URL] Unknown source type:', typeof source, source);
return null;
},
/**
* Валидация PDF файла
* @param {File} file - Файл для валидации
* @param {number} maxSizeMB - Максимальный размер в MB (по умолчанию 10)
* @returns {string|null} Сообщение об ошибке или null
*/
VALIDATE_PDF(file, maxSizeMB = 10) {
if (file.type !== 'application/pdf') {
return 'Разрешены только PDF файлы';
}
if (file.size > maxSizeMB * 1024 * 1024) {
return `Размер файла не должен превышать ${maxSizeMB}MB`;
}
return null;
},
/**
* Получение инициалов из имени (Фамилия Имя -> ФИ)
* @param {string} name - Полное имя
* @returns {string} Инициалы (первые 2 буквы)
*/
GET_INITIALS(name) {
if (!name) return '';
return name
.split(' ')
.map(n => n[0])
.join('')
.toUpperCase()
.slice(0, 2);
},
/**
* Подтверждение и удаление через Inertia
* @param {Object} entity - Объект для удаления
* @param {string} routeName - Имя роута для удаления
* @param {Object} options - Дополнительные опции
*/
CONFIRM_AND_DELETE(entity, routeName, options = {}) {
const message = options.message || `Вы уверены, что хотите удалить "${entity.title || entity.name}"?`;
if (confirm(message)) {
this.$inertia.delete(route(routeName, entity.id), {
preserveScroll: options.preserveScroll ?? true,
...options.inertiaOptions
});
}
},
/**
* Сброс фильтров и переход к чистой странице
* @param {Array<string>} filterKeys - Ключи фильтров для сброса
* @param {string} routeName - Имя роута
*/
RESET_FILTERS(filterKeys, routeName) {
filterKeys.forEach(key => {
if (Object.prototype.hasOwnProperty.call(this, key)) {
this[key] = '';
}
});
this.$inertia.get(route(routeName), {}, {
preserveState: true,
replace: true
});
},
/**
* Фильтрация через Inertia с сохранением состояния
* @param {string} routeName - Имя роута
* @param {Object} params - Параметры фильтрации
*/
INERTIA_FILTER(routeName, params) {
this.$inertia.get(route(routeName), params, {
preserveState: true
});
},
/**
* Получение базового URL для сущности
* @param {string} entityType - Тип сущности (faculty, division и т.д.)
* @returns {string} Базовый URL
*/
GET_ENTITY_BASE_URL(entityType) {
return this.GET_BASE_URL() + entityType;
},
/**
* Обработка выбора файла через input
* @param {Event} event - Событие изменения input
* @param {Function} callback - Callback для обработки файла
*/
HANDLE_FILE_SELECT(event, callback) {
const file = event.target?.files?.[0];
if (file && callback) {
callback(file);
}
},
/**
* Обработка drag & drop файла
* @param {DragEvent} event - Событие drop
* @param {Function} callback - Callback для обработки файла
*/
HANDLE_FILE_DROP(event, callback) {
this.isDragging = false;
const file = event.dataTransfer?.files?.[0];
if (file && callback) {
callback(file);
}
},
/**
* Форматирование JSON-полей в читаемый вид
* @param {string|Array|Object|null} value - JSON строка или объект
* @returns {string|null} Отформатированный текст или null
*/
FORMAT_JSON_FIELD(value) {
if (!value) return null;
let data = value;
if (typeof value === 'string') {
try {
data = JSON.parse(value);
} catch {
return value;
}
}
if (Array.isArray(data)) {
if (data.length === 0) return null;
const items = data.flatMap(item => {
if (typeof item === 'string') return item;
if (item && typeof item === 'object') {
// Образование: { year, content, institution }
if (item.content && item.institution) {
return `${item.institution} (${item.year}) — ${item.content}`;
}
// Публикации: { category_publication, publication: [{ item }] }
if (item.publication && Array.isArray(item.publication)) {
return item.publication.map(pub => pub.item || pub).filter(Boolean);
}
// Простой item: { item: "..." }
if (item.item) return item.item;
// Fallback: все значения объекта
return Object.values(item).join(', ');
}
return String(item);
});
return items.join('\n');
}
if (data && typeof data === 'object') {
// Стаж работы: { total, byProf }
if (data.total !== undefined && data.byProf !== undefined) {
return `Общий: ${data.total} лет, по профилю: ${data.byProf} лет`;
}
return Object.entries(data)
.map(([key, val]) => `${key}: ${val}`)
.join('\n');
}
return String(data);
}
}
};