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.
-
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);
+ }
}