Complete v2.0 transformation: Production-ready Flask application

Major Changes:
- Migrated from prototype to production architecture
- Implemented modular Flask app with models/services/web layers
- Added Docker containerization with docker-compose
- Switched to Pipenv for dependency management
- Built advanced parser extracting 63 real activities from INDEX_MASTER
- Implemented SQLite FTS5 full-text search
- Created minimalist, responsive web interface
- Added comprehensive documentation and deployment guides

Technical Improvements:
- Clean separation of concerns (models, services, web)
- Enhanced database schema with FTS5 indexing
- Dynamic filters populated from real data
- Production-ready configuration management
- Security best practices implementation
- Health monitoring and API endpoints

Removed Legacy Files:
- Old src/ directory structure
- Static requirements.txt (replaced by Pipfile)
- Test and debug files
- Temporary cache files

Current Status:
- 63 activities indexed across 8 categories
- Full-text search operational
- Docker deployment ready
- Production documentation complete

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-11 00:23:47 +03:00
parent ed0fc0d010
commit 4f83b8e73c
44 changed files with 6600 additions and 3620 deletions

222
app/templates/results.html Normal file
View File

@@ -0,0 +1,222 @@
{% extends "base.html" %}
{% block title %}Rezultate căutare - INDEX Sistem Jocuri{% endblock %}
{% block content %}
<div class="results-page">
<!-- Search form (compact version) -->
<form method="POST" action="{{ url_for('main.search') }}" class="search-form compact">
<div class="search-input-group">
<input
type="text"
name="search_query"
value="{{ search_query }}"
class="search-input"
placeholder="Caută activități..."
>
<button type="submit" class="search-button">Căutare</button>
</div>
{% if filters %}
<div class="filters-row">
{% if filters.category %}
<select name="category" class="filter-select compact">
<option value="">Toate categoriile</option>
{% for category in filters.category %}
<option value="{{ category }}" {% if applied_filters.category == category %}selected{% endif %}>
{{ category }}
</option>
{% endfor %}
</select>
{% endif %}
{% if filters.age_group %}
<select name="age_group" class="filter-select compact">
<option value="">Toate vârstele</option>
{% for age_group in filters.age_group %}
<option value="{{ age_group }}" {% if applied_filters.age_group == age_group %}selected{% endif %}>
{{ age_group }}
</option>
{% endfor %}
</select>
{% endif %}
{% if filters.participants %}
<select name="participants" class="filter-select compact">
<option value="">Orice număr</option>
{% for participants in filters.participants %}
<option value="{{ participants }}" {% if applied_filters.participants == participants %}selected{% endif %}>
{{ participants }}
</option>
{% endfor %}
</select>
{% endif %}
{% if filters.duration %}
<select name="duration" class="filter-select compact">
<option value="">Orice durată</option>
{% for duration in filters.duration %}
<option value="{{ duration }}" {% if applied_filters.duration == duration %}selected{% endif %}>
{{ duration }}
</option>
{% endfor %}
</select>
{% endif %}
<button type="button" class="btn btn-secondary btn-sm" onclick="clearFilters()">
Resetează
</button>
</div>
{% endif %}
</form>
<!-- Results header -->
<div class="results-header">
<h2 class="results-title">
Rezultate căutare
{% if search_query %}pentru "{{ search_query }}"{% endif %}
</h2>
<p class="results-count">
{% if results_count > 0 %}
{{ results_count }} activități găsite
{% else %}
Nu au fost găsite activități
{% endif %}
</p>
<!-- Applied filters display -->
{% if applied_filters %}
<div class="applied-filters">
<span class="applied-filters-label">Filtre aplicate:</span>
{% for filter_key, filter_value in applied_filters.items() %}
<span class="applied-filter">
{{ filter_value }}
<a href="javascript:removeFilter('{{ filter_key }}')" class="remove-filter">×</a>
</span>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Results list -->
{% if activities %}
<div class="results-list">
{% for activity in activities %}
<article class="activity-card">
<header class="activity-header">
<h3 class="activity-title">
<a href="{{ url_for('main.activity_detail', activity_id=activity.id) }}">
{{ activity.name }}
</a>
</h3>
<span class="activity-category">{{ activity.category }}</span>
</header>
<div class="activity-content">
<p class="activity-description">{{ activity.description }}</p>
<div class="activity-metadata">
{% if activity.get_age_range_display() != "toate vârstele" %}
<span class="metadata-item">
<strong>Vârsta:</strong> {{ activity.get_age_range_display() }}
</span>
{% endif %}
{% if activity.get_participants_display() != "orice număr" %}
<span class="metadata-item">
<strong>Participanți:</strong> {{ activity.get_participants_display() }}
</span>
{% endif %}
{% if activity.get_duration_display() != "durată variabilă" %}
<span class="metadata-item">
<strong>Durata:</strong> {{ activity.get_duration_display() }}
</span>
{% endif %}
{% if activity.get_materials_display() != "nu specificate" %}
<span class="metadata-item">
<strong>Materiale:</strong> {{ activity.get_materials_display() }}
</span>
{% endif %}
</div>
{% if activity.source_file %}
<div class="activity-source">
<small>Sursă: {{ activity.source_file }}</small>
</div>
{% endif %}
</div>
<footer class="activity-footer">
<a href="{{ url_for('main.activity_detail', activity_id=activity.id) }}"
class="btn btn-primary btn-sm">
Vezi detalii
</a>
</footer>
</article>
{% endfor %}
</div>
{% else %}
<div class="no-results">
<h3>Nu au fost găsite activități</h3>
<p>Încearcă să:</p>
<ul>
<li>Modifici termenii de căutare</li>
<li>Elimini unele filtre</li>
<li>Verifici ortografia</li>
<li>Folosești termeni mai generali</li>
</ul>
<a href="{{ url_for('main.index') }}" class="btn btn-primary">
Întoarce-te la căutare
</a>
</div>
{% endif %}
{% if error %}
<div class="error-message">
<strong>Eroare:</strong> {{ error }}
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
function clearFilters() {
// Clear search query and all filters
const form = document.querySelector('.search-form');
const inputs = form.querySelectorAll('input, select');
inputs.forEach(input => {
if (input.type === 'text') {
input.value = '';
} else if (input.tagName === 'SELECT') {
input.selectedIndex = 0;
}
});
// Submit the form to show all results
form.submit();
}
function removeFilter(filterKey) {
// Remove specific filter by setting its value to empty
const filterElement = document.querySelector(`[name="${filterKey}"]`);
if (filterElement) {
filterElement.value = '';
document.querySelector('.search-form').submit();
}
}
// Auto-submit on filter change
document.addEventListener('DOMContentLoaded', function() {
const filterSelects = document.querySelectorAll('.filter-select');
filterSelects.forEach(select => {
select.addEventListener('change', function() {
document.querySelector('.search-form').submit();
});
});
});
</script>
{% endblock %}