feat: setup refactoring infrastructure (Phase 0)

- Created static file structure:
  * static/css/components/ - reusable CSS components
  * static/css/pages/ - page-specific styles
  * static/js/utils/ - utility functions
  * static/js/components/ - interactive components

- Created infrastructure files:
  * static/css/variables.css - CSS custom properties
  * static/css/components/index.css - CSS entry point
  * static/js/utils/index.js - JS utilities entry point
  * static/js/components/index.js - JS components entry point

- Added comprehensive tests:
  * tests/test_static_files.py - 12 tests for structure, syntax, encoding

- Updated .gitignore:
  * Added staticfiles/ to ignore list
  * Clarified that static/ sources are tracked

- Added REFACTORING_PLAN.md:
  * New strategy: extract CSS/JS before HTML decomposition
  * Documented lessons learned from previous attempts
  * Defined phases 0-6 for systematic refactoring
This commit is contained in:
admin i 2025-10-28 08:29:43 +03:00
parent f91f81c9a7
commit 303fd54d17
11 changed files with 2159 additions and 0 deletions

3
.gitignore vendored
View File

@ -147,6 +147,9 @@ dmypy.json
# папки со статикой и медиа
media/
# Собранные статические файлы Django (collectstatic)
staticfiles/
# Исходные static файлы НЕ игнорируются (backend/static/)
# Others
node_modules

View File

@ -0,0 +1,2 @@
# Placeholder for CSS components
# This directory will contain reusable CSS components like feed-cards, ios-search, modals, etc.

View File

@ -0,0 +1,21 @@
/**
* CSS Components Index
*
* Этот файл служит единой точкой входа для всех CSS компонентов.
* Импортируйте компоненты по мере их создания.
*
* Использование в шаблонах:
* {% block extra_css %}
* <link rel="stylesheet" href="{% static 'css/components/index.css' %}">
* {% endblock %}
*/
/* Переменные (будут созданы далее) */
/* @import 'variables.css'; */
/* Общие компоненты (будут созданы в Фазе 3) */
/* @import 'feed-cards.css'; */
/* @import 'ios-search.css'; */
/* @import 'modals.css'; */
/* @import 'badges.css'; */
/* @import 'buttons.css'; */

View File

@ -0,0 +1,2 @@
# Placeholder for page-specific CSS
# This directory will contain CSS files specific to individual pages

View File

@ -0,0 +1,137 @@
/**
* CSS Variables
*
* Глобальные CSS переменные для обеспечения единообразия дизайна.
* Использование: var(--variable-name)
*
* Подключение в base.html:
* <link rel="stylesheet" href="{% static 'css/variables.css' %}">
*/
:root {
/* ========== Цветовая схема ========== */
/* Основные цвета интерфейса (наследуются от Bootstrap) */
/* --bs-primary, --bs-secondary, --bs-success, --bs-danger, --bs-warning, --bs-info */
/* Дополнительные цвета приложения */
--app-primary: #0d6efd;
--app-success: #198754;
--app-danger: #dc3545;
--app-warning: #ffc107;
--app-info: #0dcaf0;
/* Фоновые цвета */
--bg-light: #f8f9fa;
--bg-dark: #212529;
--bg-card: #ffffff;
/* Цвета текста */
--text-primary: #212529;
--text-secondary: #6c757d;
--text-muted: #adb5bd;
/* ========== Размеры и отступы ========== */
/* Высота компонентов */
--navbar-height: 60px;
--sidebar-width: 260px;
--rightbar-width: 280px;
/* Радиусы скругления */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 18px;
--radius-xl: 24px;
--radius-full: 50%;
/* Отступы */
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 2rem; /* 32px */
/* ========== Тени ========== */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05);
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.12);
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.16);
/* ========== Компоненты Feed ========== */
--feed-radius: 18px;
--feed-shadow: var(--shadow-md);
--feed-padding: 1rem;
--feed-avatar-size: 44px;
--feed-gap: 0.75rem;
/* ========== Переходы и анимации ========== */
--transition-fast: 0.15s ease;
--transition-base: 0.2s ease;
--transition-slow: 0.3s ease;
/* ========== Z-индексы ========== */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-tooltip: 1070;
/* ========== Типографика ========== */
--font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-family-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* ========== Границы ========== */
--border-width: 1px;
--border-color: #dee2e6;
--border-radius: var(--radius-md);
}
/* ========== Тёмная тема (если потребуется) ========== */
/*
@media (prefers-color-scheme: dark) {
:root {
--bg-card: #2d3748;
--text-primary: #f7fafc;
--text-secondary: #e2e8f0;
--border-color: #4a5568;
}
}
*/
/* ========== Вспомогательные классы с переменными ========== */
.shadow-sm { box-shadow: var(--shadow-sm); }
.shadow-md { box-shadow: var(--shadow-md); }
.shadow-lg { box-shadow: var(--shadow-lg); }
.shadow-xl { box-shadow: var(--shadow-xl); }
.radius-sm { border-radius: var(--radius-sm); }
.radius-md { border-radius: var(--radius-md); }
.radius-lg { border-radius: var(--radius-lg); }
.radius-xl { border-radius: var(--radius-xl); }
.radius-full { border-radius: var(--radius-full); }
.transition-fast { transition: all var(--transition-fast); }
.transition-base { transition: all var(--transition-base); }
.transition-slow { transition: all var(--transition-slow); }

View File

@ -0,0 +1,2 @@
# Placeholder for JavaScript components
# This directory will contain interactive components like employeeForm, calendarWidget, etc.

View File

@ -0,0 +1,20 @@
/**
* JavaScript Components Index
*
* Этот файл служит единой точкой входа для всех JS компонентов.
* Экспортирует все компоненты для удобного импорта.
*
* Использование в шаблонах:
* <script type="module">
* import { EmployeeForm, CalendarWidget } from '{% static "js/components/index.js" %}';
* </script>
*/
// Компоненты (будут созданы в Фазе 4)
// export { EmployeeForm } from './employeeForm.js';
// export { CalendarWidget } from './calendarWidget.js';
// export { TeamWheel } from './teamWheel.js';
// export { ListFilter } from './listFilter.js';
// Placeholder export для валидности модуля
export const COMPONENTS_VERSION = '1.0.0';

View File

@ -0,0 +1,2 @@
# Placeholder for JavaScript utilities
# This directory will contain utility functions like stringUtils, timing, scroll, etc.

View File

@ -0,0 +1,26 @@
/**
* JavaScript Utilities Index
*
* Этот файл служит единой точкой входа для всех JS утилит.
* Экспортирует все утилиты для удобного импорта.
*
* Использование в шаблонах:
* <script type="module">
* import { esc, norm, debounce } from '{% static "js/utils/index.js" %}';
* </script>
*
* Или импорт конкретной утилиты:
* import { esc } from '{% static "js/utils/stringUtils.js" %}';
*/
// Утилиты работы со строками (будут созданы в Фазе 2)
// export { esc, norm } from './stringUtils.js';
// Утилиты для работы со временем (будут созданы в Фазе 2)
// export { debounce, throttle } from './timing.js';
// Утилиты прокрутки (будут созданы в Фазе 2)
// export { smoothScrollTo } from './scroll.js';
// Placeholder export для валидности модуля
export const UTILS_VERSION = '1.0.0';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,253 @@
"""
Тесты для статических файлов (CSS/JS)
Проверяют:
1. Валидность структуры CSS файлов
2. Корректность ES6 модулей JavaScript
3. Наличие обязательных файлов
4. Отсутствие синтаксических ошибок
"""
from pathlib import Path
from django.test import TestCase
from django.conf import settings
class StaticFilesStructureTest(TestCase):
"""Тесты структуры статических файлов"""
def setUp(self):
self.static_root = Path(settings.BASE_DIR) / 'static'
self.css_root = self.static_root / 'css'
self.js_root = self.static_root / 'js'
def test_css_directories_exist(self):
"""Проверка существования CSS директорий"""
self.assertTrue(
self.css_root.exists(),
"CSS директория должна существовать"
)
self.assertTrue(
(self.css_root / 'components').exists(),
"CSS components директория должна существовать"
)
self.assertTrue(
(self.css_root / 'pages').exists(),
"CSS pages директория должна существовать"
)
def test_js_directories_exist(self):
"""Проверка существования JS директорий"""
self.assertTrue(
self.js_root.exists(),
"JS директория должна существовать"
)
self.assertTrue(
(self.js_root / 'utils').exists(),
"JS utils директория должна существовать"
)
self.assertTrue(
(self.js_root / 'components').exists(),
"JS components директория должна существовать"
)
def test_index_files_exist(self):
"""Проверка существования index файлов"""
self.assertTrue(
(self.css_root / 'components' / 'index.css').exists(),
"CSS components/index.css должен существовать"
)
self.assertTrue(
(self.js_root / 'utils' / 'index.js').exists(),
"JS utils/index.js должен существовать"
)
self.assertTrue(
(self.js_root / 'components' / 'index.js').exists(),
"JS components/index.js должен существовать"
)
def test_variables_css_exists(self):
"""Проверка существования variables.css"""
variables_file = self.css_root / 'variables.css'
self.assertTrue(
variables_file.exists(),
"variables.css должен существовать"
)
# Проверка содержания основных переменных
content = variables_file.read_text(encoding='utf-8')
required_vars = [
'--navbar-height',
'--sidebar-width',
'--feed-radius',
'--shadow-md',
'--transition-base'
]
for var in required_vars:
self.assertIn(
var,
content,
f"Переменная {var} должна быть определена в variables.css"
)
class CSSFilesValidityTest(TestCase):
"""Тесты валидности CSS файлов"""
def setUp(self):
self.css_root = Path(settings.BASE_DIR) / 'static' / 'css'
def test_css_files_syntax(self):
"""Базовая проверка синтаксиса CSS файлов"""
css_files = list(self.css_root.rglob('*.css'))
for css_file in css_files:
with self.subTest(file=css_file.name):
content = css_file.read_text(encoding='utf-8')
# Проверка парности фигурных скобок
open_braces = content.count('{')
close_braces = content.count('}')
self.assertEqual(
open_braces,
close_braces,
f"{css_file.name}: несбалансированные фигурные скобки"
)
# Проверка что нет незакрытых комментариев
# Простая проверка: /* должно быть равно */
open_comments = content.count('/*')
close_comments = content.count('*/')
self.assertEqual(
open_comments,
close_comments,
f"{css_file.name}: незакрытые комментарии"
)
def test_css_files_encoding(self):
"""Проверка кодировки CSS файлов (UTF-8)"""
css_files = list(self.css_root.rglob('*.css'))
for css_file in css_files:
with self.subTest(file=css_file.name):
try:
css_file.read_text(encoding='utf-8')
except UnicodeDecodeError:
msg = f"{css_file.name}: неверная кодировка (не UTF-8)"
self.fail(msg)
class JSFilesValidityTest(TestCase):
"""Тесты валидности JavaScript файлов"""
def setUp(self):
self.js_root = Path(settings.BASE_DIR) / 'static' / 'js'
def test_js_files_are_modules(self):
"""Проверка что JS файлы используют ES6 модули"""
js_files = list(self.js_root.rglob('*.js'))
for js_file in js_files:
with self.subTest(file=js_file.name):
content = js_file.read_text(encoding='utf-8')
# Проверка что файл содержит export (ES6 модуль)
self.assertTrue(
'export' in content,
f"{js_file.name}: должен содержать export для ES6 модуля"
)
def test_js_files_syntax_basic(self):
"""Базовая проверка синтаксиса JS файлов"""
js_files = list(self.js_root.rglob('*.js'))
for js_file in js_files:
with self.subTest(file=js_file.name):
content = js_file.read_text(encoding='utf-8')
# Проверка парности фигурных скобок
open_braces = content.count('{')
close_braces = content.count('}')
self.assertEqual(
open_braces,
close_braces,
f"{js_file.name}: несбалансированные фигурные скобки"
)
# Проверка парности круглых скобок
open_parens = content.count('(')
close_parens = content.count(')')
self.assertEqual(
open_parens,
close_parens,
f"{js_file.name}: несбалансированные круглые скобки"
)
# Проверка парности квадратных скобок
open_brackets = content.count('[')
close_brackets = content.count(']')
self.assertEqual(
open_brackets,
close_brackets,
f"{js_file.name}: несбалансированные квадратные скобки"
)
def test_js_files_encoding(self):
"""Проверка кодировки JS файлов (UTF-8)"""
js_files = list(self.js_root.rglob('*.js'))
for js_file in js_files:
with self.subTest(file=js_file.name):
try:
js_file.read_text(encoding='utf-8')
except UnicodeDecodeError:
self.fail(f"{js_file.name}: неверная кодировка (не UTF-8)")
class IndexFilesTest(TestCase):
"""Тесты для index файлов"""
def test_css_index_imports(self):
"""Проверка что CSS index.css готов к импорту компонентов"""
index_file = (
Path(settings.BASE_DIR) / 'static' / 'css' /
'components' / 'index.css'
)
content = index_file.read_text(encoding='utf-8')
# Проверка что есть комментарии о подключении
self.assertIn(
'@import',
content,
"index.css должен содержать примеры @import"
)
def test_js_utils_index_exports(self):
"""Проверка что JS utils/index.js готов к экспорту утилит"""
index_file = (
Path(settings.BASE_DIR) / 'static' / 'js' /
'utils' / 'index.js'
)
content = index_file.read_text(encoding='utf-8')
# Проверка что есть export
self.assertIn(
'export',
content,
"utils/index.js должен содержать export"
)
def test_js_components_index_exports(self):
"""Проверка что JS components/index.js готов к экспорту"""
index_file = (
Path(settings.BASE_DIR) / 'static' / 'js' /
'components' / 'index.js'
)
content = index_file.read_text(encoding='utf-8')
# Проверка что есть export
self.assertIn(
'export',
content,
"components/index.js должен содержать export"
)