implemented step 07
- added categories for 1. step in wizard
This commit is contained in:
55
data/categories.json
Normal file
55
data/categories.json
Normal file
@ -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"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -22,9 +22,21 @@
|
||||
<div class="step active" data-step="1">
|
||||
<h2>Oblasť podnikania</h2>
|
||||
<p class="step-description">V čom podnikáte? Pomôže nám to prispôsobiť váš web.</p>
|
||||
<div class="form-placeholder">
|
||||
<!-- Budúce formulárové prvky -->
|
||||
<p>Obsah pre krok 1...</p>
|
||||
|
||||
<div id="category-list" class="category-grid">
|
||||
<!-- Categories will be injected here -->
|
||||
</div>
|
||||
|
||||
<div id="subcategory-container" class="subcategory-section hidden">
|
||||
<h3>Vyberte podkategóriu</h3>
|
||||
<div id="subcategory-list" class="subcategory-chips">
|
||||
<!-- Subcategories will be injected here -->
|
||||
</div>
|
||||
|
||||
<div id="custom-category-group" class="form-group hidden">
|
||||
<label for="custom-description">Popíšte vašu oblasť podnikania</label>
|
||||
<textarea id="custom-description" rows="3" placeholder="Napr. Súkromná materská škola, Prenájom jácht..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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 = `
|
||||
<div class="category-icon">${icons[cat.id] || '📁'}</div>
|
||||
<div class="category-name">${cat.name}</div>
|
||||
`;
|
||||
|
||||
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}%`;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user