diff --git a/data/categories.json b/data/categories.json new file mode 100644 index 0000000..a06ecb5 --- /dev/null +++ b/data/categories.json @@ -0,0 +1,55 @@ +{ + "categories": [ + { + "id": "gastro", + "name": "Gastro", + "subcategories": [ + {"id": "restaurant", "name": "Reštaurácia"}, + {"id": "pizza", "name": "Pizzeria"}, + {"id": "cafe", "name": "Kaviareň / Bistro"}, + {"id": "bar", "name": "Bar / Pub"}, + {"id": "catering", "name": "Catering"} + ] + }, + { + "id": "beauty", + "name": "Krása a Zdravie", + "subcategories": [ + {"id": "barber", "name": "Barber / Kaderníctvo"}, + {"id": "cosmetics", "name": "Kozmetický salón"}, + {"id": "nails", "name": "Manikúra / Pedikúra"}, + {"id": "fitness", "name": "Fitness / Gym"}, + {"id": "dentist", "name": "Zubár / Poliklinika"} + ] + }, + { + "id": "crafts", + "name": "Remeslá a Služby", + "subcategories": [ + {"id": "plumber", "name": "Inštalatér"}, + {"id": "electrician", "name": "Elektrikár"}, + {"id": "builder", "name": "Stavebné práce"}, + {"id": "mechanic", "name": "Autoservis"}, + {"id": "cleaning", "name": "Upratovacie služby"} + ] + }, + { + "id": "professional", + "name": "Odborné služby", + "subcategories": [ + {"id": "lawyer", "name": "Právnik / Advokát"}, + {"id": "accountant", "name": "Účtovník"}, + {"id": "consulting", "name": "Konzultant / Kouč"}, + {"id": "marketing", "name": "Marketingová agentúra"}, + {"id": "it_services", "name": "IT služby / Software"} + ] + }, + { + "id": "other", + "name": "Iné", + "subcategories": [ + {"id": "custom", "name": "Vlastná kategória"} + ] + } + ] +} diff --git a/public/ajax.php b/public/ajax.php index 7270e16..49f28c4 100644 --- a/public/ajax.php +++ b/public/ajax.php @@ -63,6 +63,11 @@ try { sendResponse(true, $projectActions->initSession()); break; + case 'getCategories': + $storage = new \App\Services\FileStorage(); + sendResponse(true, $storage->get('categories.json')); + break; + case 'createProject': sendResponse(true, $projectActions->createProject($userId)); break; @@ -79,6 +84,19 @@ try { sendResponse(true, $projectActions->getProjectStatus($userId, $projectId)); break; + case 'saveStep': + $projectId = $data['project_id'] ?? null; + $step = (int)($data['payload']['step'] ?? 0); + $payloadData = $data['payload']['data'] ?? null; + + if (!$projectId || !$step || !$payloadData) { + sendResponse(false, ['code' => 'MISSING_DATA', 'message' => 'Project ID, step and data are required.'], 400); + } + + $success = $projectActions->saveStep($userId, $projectId, $step, $payloadData); + sendResponse($success, ['message' => 'Step saved successfully.']); + break; + case 'saveConsent': $projectId = $data['project_id'] ?? null; $consentText = $data['payload']['consent_text'] ?? null; diff --git a/public/css/wizard.css b/public/css/wizard.css index 247db42..3c524a5 100644 --- a/public/css/wizard.css +++ b/public/css/wizard.css @@ -127,6 +127,110 @@ button { background-color: var(--primary-hover); } +/* Category Cards */ +.category-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 1rem; + margin-bottom: 2rem; +} + +.category-card { + border: 2px solid var(--border-color); + border-radius: 0.5rem; + padding: 1.5rem 1rem; + text-align: center; + cursor: pointer; + transition: all 0.2s; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.category-card:hover { + border-color: var(--primary-color); + background-color: #eff6ff; +} + +.category-card.selected { + border-color: var(--primary-color); + background-color: #eff6ff; + box-shadow: 0 0 0 1px var(--primary-color); +} + +.category-icon { + font-size: 2rem; + margin-bottom: 0.5rem; +} + +.category-name { + font-weight: 600; + font-size: 0.9375rem; +} + +/* Subcategories */ +.subcategory-section { + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px dashed var(--border-color); +} + +.subcategory-chips { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 1rem; +} + +.chip { + padding: 0.5rem 1rem; + border: 1px solid var(--border-color); + border-radius: 2rem; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s; + background-color: white; +} + +.chip:hover { + border-color: var(--primary-color); + color: var(--primary-color); +} + +.chip.selected { + background-color: var(--primary-color); + border-color: var(--primary-color); + color: white; +} + +/* Form controls */ +.form-group { + margin-top: 1.5rem; +} + +label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + font-size: 0.875rem; +} + +textarea, input[type="text"] { + width: 100%; + padding: 0.75rem; + border: 1px solid var(--border-color); + border-radius: 0.375rem; + font-family: inherit; + font-size: 0.875rem; +} + +textarea:focus, input[type="text"]:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + button:disabled { opacity: 0.5; cursor: not-allowed; diff --git a/public/index.html b/public/index.html index 7c94357..667206c 100644 --- a/public/index.html +++ b/public/index.html @@ -22,9 +22,21 @@

Oblasť podnikania

V čom podnikáte? Pomôže nám to prispôsobiť váš web.

-
- -

Obsah pre krok 1...

+ +
+ +
+ +
diff --git a/public/js/wizard.js b/public/js/wizard.js index e4c0a75..c324a51 100644 --- a/public/js/wizard.js +++ b/public/js/wizard.js @@ -7,7 +7,13 @@ const App = { userId: localStorage.getItem('ww_user_id'), currentStep: 1, totalSteps: 8, - project: null + project: null, + categories: [], + selection: { + category: null, + subcategory: null, + customDescription: '' + } }, async init() { @@ -17,10 +23,54 @@ const App = { await this.initSession(); } + await this.loadCategories(); this.bindEvents(); + + // If we don't have a project, try to load existing or create one + if (!this.state.project) { + await this.loadLastProject(); + } + this.showStep(this.state.currentStep); }, + async loadLastProject() { + try { + const response = await this.apiCall('listProjects'); + if (response.success && response.data.length > 0) { + // Load the most recent project + const lastProject = response.data[0]; + const statusResponse = await this.apiCall('getProjectStatus', {}, lastProject.project_id); + if (statusResponse.success) { + this.state.project = statusResponse.data; + this.state.currentStep = this.state.project.current_step || 1; + console.log('Loaded existing project:', this.state.project.project_id); + this.syncSelectionWithProject(); + } + } else { + await this.createProject(); + } + } catch (error) { + console.error('Failed to load projects:', error); + await this.createProject(); + } + }, + + syncSelectionWithProject() { + if (this.state.project && this.state.project.wizard_data.business_category) { + const bc = this.state.project.wizard_data.business_category; + this.state.selection.category = bc.group; + this.state.selection.subcategory = bc.subcategory; + this.state.selection.customDescription = bc.custom_description || ''; + + if (this.state.selection.category) { + this.selectCategory(this.state.selection.category); + this.state.selection.subcategory = bc.subcategory; // restore after selectCategory resets it + this.renderSubcategories(this.state.selection.category); + } + } + }, + async initSession() { try { const response = await this.apiCall('initSession'); @@ -31,14 +81,38 @@ const App = { } } catch (error) { console.error('Failed to initialize session:', error); - alert('Chyba pri inicializácii session. Skontrolujte pripojenie.'); + alert('Chyba pri inicializácii session.'); + } + }, + + async createProject() { + try { + const response = await this.apiCall('createProject'); + if (response.success) { + this.state.project = response.data; + console.log('Project created:', this.state.project.project_id); + } + } catch (error) { + console.error('Failed to create project:', error); + } + }, + + async loadCategories() { + try { + const response = await this.apiCall('getCategories'); + if (response.success) { + this.state.categories = response.data.categories; + this.renderCategories(); + } + } catch (error) { + console.error('Failed to load categories:', error); } }, async apiCall(action, payload = {}, projectId = null) { const body = { action, - project_id: projectId, + project_id: projectId || (this.state.project ? this.state.project.project_id : null), payload }; @@ -67,6 +141,75 @@ const App = { bindEvents() { document.getElementById('btn-next').addEventListener('click', () => this.nextStep()); document.getElementById('btn-prev').addEventListener('click', () => this.prevStep()); + + document.getElementById('custom-description').addEventListener('input', (e) => { + this.state.selection.customDescription = e.target.value; + }); + }, + + renderCategories() { + const container = document.getElementById('category-list'); + container.innerHTML = ''; + + const icons = { + gastro: '🍕', + beauty: '✨', + crafts: '🛠️', + professional: '💼', + other: '❓' + }; + + this.state.categories.forEach(cat => { + const card = document.createElement('div'); + card.className = 'category-card'; + if (this.state.selection.category === cat.id) card.classList.add('selected'); + + card.innerHTML = ` +
${icons[cat.id] || '📁'}
+
${cat.name}
+ `; + + card.addEventListener('click', () => this.selectCategory(cat.id)); + container.appendChild(card); + }); + }, + + selectCategory(categoryId) { + this.state.selection.category = categoryId; + this.state.selection.subcategory = null; + + this.renderCategories(); + this.renderSubcategories(categoryId); + + document.getElementById('subcategory-container').classList.remove('hidden'); + + if (categoryId === 'other') { + document.getElementById('custom-category-group').classList.remove('hidden'); + } else { + document.getElementById('custom-category-group').classList.add('hidden'); + } + }, + + renderSubcategories(categoryId) { + const container = document.getElementById('subcategory-list'); + container.innerHTML = ''; + + const category = this.state.categories.find(c => c.id === categoryId); + if (!category) return; + + category.subcategories.forEach(sub => { + const chip = document.createElement('div'); + chip.className = 'chip'; + if (this.state.selection.subcategory === sub.id) chip.classList.add('selected'); + chip.textContent = sub.name; + + chip.addEventListener('click', () => { + this.state.selection.subcategory = sub.id; + this.renderSubcategories(categoryId); + }); + + container.appendChild(chip); + }); }, showStep(n) { @@ -82,7 +225,31 @@ const App = { this.updateUI(); }, - nextStep() { + async nextStep() { + if (this.state.currentStep === 1) { + if (!this.state.selection.category || !this.state.selection.subcategory) { + alert('Prosím, vyberte kategóriu aj podkategóriu.'); + return; + } + + try { + await this.apiCall('saveStep', { + step: 1, + data: { + business_category: { + group: this.state.selection.category, + subcategory: this.state.selection.subcategory, + custom_description: this.state.selection.category === 'other' ? this.state.selection.customDescription : null + } + } + }); + } catch (error) { + console.error('Save step failed:', error); + alert('Nepodarilo sa uložiť dáta.'); + return; + } + } + if (this.state.currentStep < this.state.totalSteps) { this.showStep(this.state.currentStep + 1); } @@ -95,7 +262,6 @@ const App = { }, updateUI() { - // Update buttons const btnPrev = document.getElementById('btn-prev'); const btnNext = document.getElementById('btn-next'); @@ -107,7 +273,6 @@ const App = { btnNext.textContent = 'Pokračovať'; } - // Update progress bar const progressFill = document.querySelector('.progress-fill'); const percent = ((this.state.currentStep - 1) / (this.state.totalSteps - 1)) * 100; progressFill.style.width = `${percent}%`; diff --git a/src/Actions/ProjectActions.php b/src/Actions/ProjectActions.php index 9a14711..e661f71 100644 --- a/src/Actions/ProjectActions.php +++ b/src/Actions/ProjectActions.php @@ -120,4 +120,29 @@ class ProjectActions return $projectData; } + + /** + * Saves data for a specific wizard step. + */ + public function saveStep(string $userId, string $projectId, int $step, array $data): bool + { + $projectPath = "projects/{$projectId}.json"; + $projectData = $this->getProjectStatus($userId, $projectId); + + switch ($step) { + case 1: + if (!isset($data['business_category'])) { + throw new Exception("Missing business_category data.", 400); + } + $projectData['wizard_data']['business_category'] = $data['business_category']; + break; + + // More steps will be added later + } + + $projectData['current_step'] = max($projectData['current_step'], $step + 1); + $projectData['updated_at'] = date('c'); + + return $this->storage->put($projectPath, $projectData); + } }