fix: show location (country, city, district) in product edit modal
- JSON endpoint now joins locations+categories+subcategories for full info - Edit form shows location tags (country, city, district) at top - Location hidden when adding new product (no location yet) - All fields properly filled by fillEditForm on edit - Category and subcategory side by side in edit form
This commit is contained in:
@@ -438,6 +438,26 @@ pre { font-size: 0.8rem; white-space: pre-wrap; word-break: break-all; max-width
|
|||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pf-location-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.4rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-loc-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
background: #e0e7ff;
|
||||||
|
color: #3730a3;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-location {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|||||||
@@ -48,7 +48,16 @@ router.post('/products/:id/delete', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/products/:id/json', async (req, res) => {
|
router.get('/products/:id/json', async (req, res) => {
|
||||||
const p = await db.getAsync('SELECT * FROM products WHERE id=?', [req.params.id]);
|
const p = await db.getAsync(
|
||||||
|
`SELECT p.*, l.country, l.city, l.district, c.name as category_name,
|
||||||
|
COALESCE(sc.name, '') as subcategory_name
|
||||||
|
FROM products p
|
||||||
|
LEFT JOIN locations l ON p.location_id = l.id
|
||||||
|
LEFT JOIN categories c ON p.category_id = c.id
|
||||||
|
LEFT JOIN subcategories sc ON p.subcategory_id = sc.id
|
||||||
|
WHERE p.id = ?`,
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
if (!p) return res.status(404).json({ error: 'Not found' });
|
if (!p) return res.status(404).json({ error: 'Not found' });
|
||||||
res.json(p);
|
res.json(p);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ document.querySelectorAll('.tree-toggle').forEach(el=>{el.addEventListener('clic
|
|||||||
const loc=el.dataset.loc, cat=el.dataset.cat, sub=el.dataset.sub, all=el.dataset.all;
|
const loc=el.dataset.loc, cat=el.dataset.cat, sub=el.dataset.sub, all=el.dataset.all;
|
||||||
if(loc||cat||sub||all){ let u='/catalog?'; if(loc) u+='loc='+loc; if(cat) u+='cat='+cat; if(sub) u+='sub='+sub; location.href=u; }
|
if(loc||cat||sub||all){ let u='/catalog?'; if(loc) u+='loc='+loc; if(cat) u+='cat='+cat; if(sub) u+='sub='+sub; location.href=u; }
|
||||||
})});
|
})});
|
||||||
function openAdd(){ document.getElementById('modal-body').innerHTML=addFormTpl; document.getElementById('product-modal').style.display='flex'; }
|
function openAdd(){ document.getElementById('modal-body').innerHTML=addFormTpl; const loc=document.getElementById('pf-location'); if(loc)loc.style.display='none'; document.getElementById('product-modal').style.display='flex'; }
|
||||||
async function openEdit(id){ const r=await fetch('/catalog/products/'+id+'/json'); const p=await r.json();
|
async function openEdit(id){ const r=await fetch('/catalog/products/'+id+'/json'); const p=await r.json();
|
||||||
document.getElementById('modal-body').innerHTML=editFormTpl.replace('/__ID__/','/'+p.id+'/'); fillEditForm(p); document.getElementById('product-modal').style.display='flex'; }
|
document.getElementById('modal-body').innerHTML=editFormTpl.replace('/__ID__/','/'+p.id+'/'); fillEditForm(p); document.getElementById('product-modal').style.display='flex'; }
|
||||||
function fillEditForm(p){ const f=document.getElementById('product-modal').querySelector('form'); if(!f)return;
|
function fillEditForm(p){ const f=document.getElementById('product-modal').querySelector('form'); if(!f)return;
|
||||||
@@ -84,7 +84,13 @@ function fillEditForm(p){ const f=document.getElementById('product-modal').query
|
|||||||
f.querySelector('[name=photo_url]').value=p.photo_url||''; f.querySelector('[name=hidden_photo_url]').value=p.hidden_photo_url||'';
|
f.querySelector('[name=photo_url]').value=p.photo_url||''; f.querySelector('[name=hidden_photo_url]').value=p.hidden_photo_url||'';
|
||||||
f.querySelector('[name=hidden_coordinates]').value=p.hidden_coordinates||''; f.querySelector('[name=hidden_description]').value=p.hidden_description||'';
|
f.querySelector('[name=hidden_coordinates]').value=p.hidden_coordinates||''; f.querySelector('[name=hidden_description]').value=p.hidden_description||'';
|
||||||
f.querySelector('[name=private_data]').value=p.private_data||''; f.querySelector('[name=category_id]').value=p.category_id||'';
|
f.querySelector('[name=private_data]').value=p.private_data||''; f.querySelector('[name=category_id]').value=p.category_id||'';
|
||||||
updateSubcats(p.category_id,p.subcategory_id); }
|
updateSubcats(p.category_id,p.subcategory_id);
|
||||||
|
const loc=document.getElementById('pf-location');
|
||||||
|
if(loc&&(p.country||p.city||p.district)){
|
||||||
|
loc.style.display=''; document.getElementById('pf-country').textContent=p.country||'';
|
||||||
|
document.getElementById('pf-city').textContent=p.city||''; document.getElementById('pf-district').textContent=p.district||'';
|
||||||
|
} else if(loc){loc.style.display='none';}
|
||||||
|
}
|
||||||
function updateSubcats(catId,selSub){ const ss=document.getElementById('product-modal').querySelector('[name=subcategory_id]'); if(!ss)return;
|
function updateSubcats(catId,selSub){ const ss=document.getElementById('product-modal').querySelector('[name=subcategory_id]'); if(!ss)return;
|
||||||
ss.innerHTML='<option value="">-- Subcategory --</option>'; subcats.forEach(s=>{if(s.category_id==catId){const o=document.createElement('option');o.value=s.id;o.textContent=s.name;if(s.id==selSub)o.selected=true;ss.appendChild(o)}}); }
|
ss.innerHTML='<option value="">-- Subcategory --</option>'; subcats.forEach(s=>{if(s.category_id==catId){const o=document.createElement('option');o.value=s.id;o.textContent=s.name;if(s.id==selSub)o.selected=true;ss.appendChild(o)}}); }
|
||||||
document.getElementById('product-modal').addEventListener('click',e=>{if(e.target===document.getElementById('product-modal'))document.getElementById('product-modal').style.display='none'});
|
document.getElementById('product-modal').addEventListener('click',e=>{if(e.target===document.getElementById('product-modal'))document.getElementById('product-modal').style.display='none'});
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ export function renderProductEditForm(action, catOptions, subcatJson) {
|
|||||||
const title = isEdit ? 'Edit Product' : 'Add Product';
|
const title = isEdit ? 'Edit Product' : 'Add Product';
|
||||||
return `<h2>${title}</h2>
|
return `<h2>${title}</h2>
|
||||||
<form method="POST" action="${action}" enctype="multipart/form-data" class="product-form">
|
<form method="POST" action="${action}" enctype="multipart/form-data" class="product-form">
|
||||||
|
<div class="pf-group pf-location" id="pf-location" style="display:none">
|
||||||
|
<label>Location</label>
|
||||||
|
<div class="pf-location-row">
|
||||||
|
<span class="pf-loc-tag" id="pf-country"></span>
|
||||||
|
<span class="pf-loc-tag" id="pf-city"></span>
|
||||||
|
<span class="pf-loc-tag" id="pf-district"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="pf-group">
|
<div class="pf-group">
|
||||||
<label>Name</label>
|
<label>Name</label>
|
||||||
<input name="name" required placeholder="Product name">
|
<input name="name" required placeholder="Product name">
|
||||||
@@ -17,6 +25,7 @@ export function renderProductEditForm(action, catOptions, subcatJson) {
|
|||||||
<input name="quantity_in_stock" type="number" min="0" value="0" placeholder="0">
|
<input name="quantity_in_stock" type="number" min="0" value="0" placeholder="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pf-row">
|
||||||
<div class="pf-group">
|
<div class="pf-group">
|
||||||
<label>Category</label>
|
<label>Category</label>
|
||||||
<select name="category_id" required onchange="updateSubcats(this.value)">
|
<select name="category_id" required onchange="updateSubcats(this.value)">
|
||||||
@@ -27,6 +36,7 @@ export function renderProductEditForm(action, catOptions, subcatJson) {
|
|||||||
<label>Subcategory</label>
|
<label>Subcategory</label>
|
||||||
<select name="subcategory_id"><option value="">-- Subcategory --</option></select>
|
<select name="subcategory_id"><option value="">-- Subcategory --</option></select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="pf-group">
|
<div class="pf-group">
|
||||||
<label>Description</label>
|
<label>Description</label>
|
||||||
<textarea name="description" rows="3" placeholder="Public description"></textarea>
|
<textarea name="description" rows="3" placeholder="Public description"></textarea>
|
||||||
@@ -49,12 +59,10 @@ export function renderProductEditForm(action, catOptions, subcatJson) {
|
|||||||
<label>Or Upload Hidden Photo</label>
|
<label>Or Upload Hidden Photo</label>
|
||||||
<input type="file" name="hidden_photo_file" accept="image/*" class="pf-file">
|
<input type="file" name="hidden_photo_file" accept="image/*" class="pf-file">
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-row">
|
|
||||||
<div class="pf-group">
|
<div class="pf-group">
|
||||||
<label>Hidden Coordinates</label>
|
<label>Hidden Coordinates</label>
|
||||||
<input name="hidden_coordinates" placeholder="lat,lng">
|
<input name="hidden_coordinates" placeholder="lat,lng">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="pf-group">
|
<div class="pf-group">
|
||||||
<label>Hidden Description</label>
|
<label>Hidden Description</label>
|
||||||
<textarea name="hidden_description" rows="2" placeholder="Shown after purchase"></textarea>
|
<textarea name="hidden_description" rows="2" placeholder="Shown after purchase"></textarea>
|
||||||
|
|||||||
Reference in New Issue
Block a user