sections!

This commit is contained in:
2025-06-08 17:07:31 +02:00
parent b616369b6f
commit 3a4e7bf41b
23 changed files with 322 additions and 48 deletions

142
app.py
View File

@@ -53,6 +53,15 @@ class Product(db.Model):
def __repr__(self): def __repr__(self):
return f"Product('{self.name}')" return f"Product('{self.name}')"
class Section(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False, unique=True)
description = db.Column(db.Text, nullable=True)
icon = db.Column(db.String(200), nullable=True)
def __repr__(self):
return f"Section('{self.name}')"
class Plant(db.Model): class Plant(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) name = db.Column(db.String(100), nullable=False)
@@ -64,6 +73,7 @@ class Plant(db.Model):
size_id = db.Column(db.Integer, db.ForeignKey('size.id'), nullable=True) size_id = db.Column(db.Integer, db.ForeignKey('size.id'), nullable=True)
care_difficulty_id = db.Column(db.Integer, db.ForeignKey('care_difficulty.id'), nullable=True) care_difficulty_id = db.Column(db.Integer, db.ForeignKey('care_difficulty.id'), nullable=True)
growth_rate_id = db.Column(db.Integer, db.ForeignKey('growth_rate.id'), nullable=True) growth_rate_id = db.Column(db.Integer, db.ForeignKey('growth_rate.id'), nullable=True)
section_id = db.Column(db.Integer, db.ForeignKey('section.id'), nullable=True)
products = db.Column(db.Text, nullable=True) # Will store product IDs as comma-separated values products = db.Column(db.Text, nullable=True) # Will store product IDs as comma-separated values
description = db.Column(db.Text, nullable=True) description = db.Column(db.Text, nullable=True)
date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
@@ -76,6 +86,7 @@ class Plant(db.Model):
size = db.relationship('Size', backref='plant_size') size = db.relationship('Size', backref='plant_size')
care_difficulty = db.relationship('CareDifficulty', backref='plant_care_difficulty') care_difficulty = db.relationship('CareDifficulty', backref='plant_care_difficulty')
growth_rate = db.relationship('GrowthRate', backref='plant_growth_rate') growth_rate = db.relationship('GrowthRate', backref='plant_growth_rate')
section = db.relationship('Section', backref='plants')
def __repr__(self): def __repr__(self):
return f"Plant('{self.name}', '{self.date_added}')" return f"Plant('{self.name}', '{self.date_added}')"
@@ -140,13 +151,15 @@ def inject_admin_counts():
climate_count = Climate.query.count() climate_count = Climate.query.count()
product_count = Product.query.count() product_count = Product.query.count()
plant_count = Plant.query.count() plant_count = Plant.query.count()
section_count = Section.query.count()
except Exception: except Exception:
environment_count = climate_count = product_count = plant_count = 0 environment_count = climate_count = product_count = plant_count = section_count = 0
return dict( return dict(
environment_count=environment_count, environment_count=environment_count,
climate_count=climate_count, climate_count=climate_count,
product_count=product_count, product_count=product_count,
plant_count=plant_count plant_count=plant_count,
section_count=section_count
) )
def none_if_empty(val): def none_if_empty(val):
@@ -1126,6 +1139,91 @@ def delete_growth_rate(rate_id):
flash('Growth rate category deleted successfully!', 'success') flash('Growth rate category deleted successfully!', 'success')
return redirect(url_for('manage_growth_rates')) return redirect(url_for('manage_growth_rates'))
# Section management routes
@app.route('/admin/sections', methods=['GET', 'POST'])
def manage_sections():
if not is_logged_in():
return redirect(url_for('login'))
if request.method == 'POST':
name = request.form['name']
description = request.form['description']
icon_file = request.files.get('icon')
icon_filename = None
if not name:
flash('Name is required!', 'danger')
print('No name provided')
return redirect(url_for('manage_sections'))
if Section.query.filter_by(name=name).first():
flash('Section with this name already exists!', 'danger')
print('Duplicate section name')
return redirect(url_for('manage_sections'))
if icon_file and icon_file.filename:
icon_filename = secure_filename(icon_file.filename)
icon_path = os.path.join('static/icons', icon_filename)
os.makedirs('static/icons', exist_ok=True)
try:
icon_file.save(icon_path)
except Exception as e:
print('Error saving icon:', e)
flash(f'Error saving icon: {e}', 'danger')
try:
section = Section(name=name, description=description, icon=icon_filename)
db.session.add(section)
db.session.commit()
flash('Section added successfully!', 'success')
print('Section added:', name)
except Exception as e:
db.session.rollback()
print('Error adding section:', e)
flash(f'Error adding section: {e}', 'danger')
return redirect(url_for('manage_sections'))
sections = Section.query.all()
return render_template('manage_sections.html',
sections=sections,
plant_count=len(Plant.query.all()),
climate_count=len(Climate.query.all()),
environment_count=len(Environment.query.all()),
product_count=len(Product.query.all()),
light_count=len(Light.query.all()),
toxicity_count=len(Toxicity.query.all()),
size_count=len(Size.query.all()),
difficulty_count=len(CareDifficulty.query.all()),
rate_count=len(GrowthRate.query.all()),
section_count=len(sections))
@app.route('/admin/sections/edit/<int:section_id>', methods=['GET', 'POST'])
def edit_section(section_id):
if not is_logged_in():
return redirect(url_for('login'))
section = Section.query.get_or_404(section_id)
if request.method == 'POST':
section.name = request.form['name']
section.description = request.form['description']
icon_file = request.files.get('icon')
if icon_file and icon_file.filename:
icon_filename = secure_filename(icon_file.filename)
icon_path = os.path.join('static/icons', icon_filename)
os.makedirs('static/icons', exist_ok=True)
try:
icon_file.save(icon_path)
except Exception as e:
print('Error saving icon:', e)
section.icon = icon_filename
db.session.commit()
flash('Section updated successfully!', 'success')
return redirect(url_for('manage_sections'))
return render_template('edit_section.html', section=section)
@app.route('/admin/sections/delete/<int:section_id>', methods=['POST'])
def delete_section(section_id):
if not is_logged_in():
return redirect(url_for('login'))
section = Section.query.get_or_404(section_id)
db.session.delete(section)
db.session.commit()
flash('Section deleted successfully!', 'success')
return redirect(url_for('manage_sections'))
def seed_db(): def seed_db():
# Environments # Environments
environments = [ environments = [
@@ -1137,6 +1235,18 @@ def seed_db():
if not Environment.query.filter_by(name=env['name']).first(): if not Environment.query.filter_by(name=env['name']).first():
db.session.add(Environment(name=env['name'], description=env['description'])) db.session.add(Environment(name=env['name'], description=env['description']))
# Sections
sections = [
{'name': 'Succulents', 'description': 'Plants that store water in their leaves, stems, or roots.'},
{'name': 'Tropical', 'description': 'Plants native to tropical regions.'},
{'name': 'Herbs', 'description': 'Plants used for culinary, medicinal, or aromatic purposes.'},
{'name': 'Flowering', 'description': 'Plants grown primarily for their flowers.'},
{'name': 'Foliage', 'description': 'Plants grown primarily for their attractive leaves.'}
]
for sec in sections:
if not Section.query.filter_by(name=sec['name']).first():
db.session.add(Section(name=sec['name'], description=sec['description']))
# Climates # Climates
climates = [ climates = [
{'name': 'Tropical', 'description': 'Warm and humid year-round.'}, {'name': 'Tropical', 'description': 'Warm and humid year-round.'},
@@ -1234,56 +1344,63 @@ def seed_db():
easy = CareDifficulty.query.filter_by(name='Easy').first() easy = CareDifficulty.query.filter_by(name='Easy').first()
moderate = GrowthRate.query.filter_by(name='Moderate').first() moderate = GrowthRate.query.filter_by(name='Moderate').first()
# Get section instances
succulents = Section.query.filter_by(name='Succulents').first()
tropical_section = Section.query.filter_by(name='Tropical').first()
herbs = Section.query.filter_by(name='Herbs').first()
flowering = Section.query.filter_by(name='Flowering').first()
foliage = Section.query.filter_by(name='Foliage').first()
# 10 example plants # 10 example plants
seed_plants = [ seed_plants = [
dict(name='Monstera Deliciosa', climate=tropical, environment=indoor, dict(name='Monstera Deliciosa', climate=tropical, environment=indoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=easy, growth_rate=moderate, care_difficulty=easy, growth_rate=moderate, section=tropical_section,
products=[soil, fert], description='A popular tropical houseplant with large, split leaves.', products=[soil, fert], description='A popular tropical houseplant with large, split leaves.',
care_guide='<ul><li>Water when the top 2 inches of soil are dry.</li><li>Provide bright, indirect light.</li><li>Fertilize monthly during growing season.</li></ul>'), care_guide='<ul><li>Water when the top 2 inches of soil are dry.</li><li>Provide bright, indirect light.</li><li>Fertilize monthly during growing season.</li></ul>'),
dict(name='Aloe Vera', climate=arid, environment=indoor, dict(name='Aloe Vera', climate=arid, environment=indoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=easy, growth_rate=moderate, care_difficulty=easy, growth_rate=moderate, section=succulents,
products=[soil], description='A succulent known for its medicinal properties.', products=[soil], description='A succulent known for its medicinal properties.',
care_guide='<ul><li>Allow soil to dry completely between waterings.</li><li>Place in bright, indirect sunlight.</li><li>Use well-draining soil.</li></ul>'), care_guide='<ul><li>Allow soil to dry completely between waterings.</li><li>Place in bright, indirect sunlight.</li><li>Use well-draining soil.</li></ul>'),
dict(name='Fiddle Leaf Fig', climate=tropical, environment=indoor, dict(name='Fiddle Leaf Fig', climate=tropical, environment=indoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=moderate, growth_rate=moderate, care_difficulty=moderate, growth_rate=moderate, section=foliage,
products=[soil, fert, can], description='A trendy houseplant with large, violin-shaped leaves.', products=[soil, fert, can], description='A trendy houseplant with large, violin-shaped leaves.',
care_guide='<ul><li>Keep soil consistently moist but not soggy.</li><li>Needs bright, filtered light.</li><li>Rotate plant for even growth.</li></ul>'), care_guide='<ul><li>Keep soil consistently moist but not soggy.</li><li>Needs bright, filtered light.</li><li>Rotate plant for even growth.</li></ul>'),
dict(name='Snake Plant', climate=arid, environment=indoor, dict(name='Snake Plant', climate=arid, environment=indoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=easy, growth_rate=moderate, care_difficulty=easy, growth_rate=moderate, section=foliage,
products=[soil, can], description='A hardy plant that tolerates low light and irregular watering.', products=[soil, can], description='A hardy plant that tolerates low light and irregular watering.',
care_guide='<ul><li>Water sparingly; let soil dry out between waterings.</li><li>Tolerates low to bright light.</li><li>Wipe leaves to remove dust.</li></ul>'), care_guide='<ul><li>Water sparingly; let soil dry out between waterings.</li><li>Tolerates low to bright light.</li><li>Wipe leaves to remove dust.</li></ul>'),
dict(name='Spider Plant', climate=temperate, environment=indoor, dict(name='Spider Plant', climate=temperate, environment=indoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=easy, growth_rate=moderate, care_difficulty=easy, growth_rate=moderate, section=foliage,
products=[soil, can], description='An easy-care plant with arching leaves and baby plantlets.', products=[soil, can], description='An easy-care plant with arching leaves and baby plantlets.',
care_guide='<ul><li>Keep soil slightly moist.</li><li>Thrives in bright, indirect light.</li><li>Trim brown tips as needed.</li></ul>'), care_guide='<ul><li>Keep soil slightly moist.</li><li>Thrives in bright, indirect light.</li><li>Trim brown tips as needed.</li></ul>'),
dict(name='Peace Lily', climate=tropical, environment=indoor, dict(name='Peace Lily', climate=tropical, environment=indoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=easy, growth_rate=moderate, care_difficulty=easy, growth_rate=moderate, section=flowering,
products=[soil, fert, can], description='A flowering plant that thrives in shade and purifies air.', products=[soil, fert, can], description='A flowering plant that thrives in shade and purifies air.',
care_guide='<ul><li>Water when leaves droop slightly.</li><li>Prefers low to medium light.</li><li>Remove spent flowers to encourage new blooms.</li></ul>'), care_guide='<ul><li>Water when leaves droop slightly.</li><li>Prefers low to medium light.</li><li>Remove spent flowers to encourage new blooms.</li></ul>'),
dict(name='Jade Plant', climate=arid, environment=indoor, dict(name='Jade Plant', climate=arid, environment=indoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=easy, growth_rate=moderate, care_difficulty=easy, growth_rate=moderate, section=succulents,
products=[soil], description='A succulent with thick, shiny leaves and a tree-like form.', products=[soil], description='A succulent with thick, shiny leaves and a tree-like form.',
care_guide='<ul><li>Let soil dry between waterings.</li><li>Needs several hours of direct sunlight.</li><li>Prune to maintain shape.</li></ul>'), care_guide='<ul><li>Let soil dry between waterings.</li><li>Needs several hours of direct sunlight.</li><li>Prune to maintain shape.</li></ul>'),
dict(name='Tomato', climate=temperate, environment=outdoor, dict(name='Tomato', climate=temperate, environment=outdoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=moderate, growth_rate=moderate, care_difficulty=moderate, growth_rate=moderate, section=herbs,
products=[soil, fert, can], description='A classic edible plant for outdoor gardens.', products=[soil, fert, can], description='A classic edible plant for outdoor gardens.',
care_guide='<ul><li>Water regularly, keeping soil consistently moist.</li><li>Provide full sun.</li><li>Support with stakes or cages.</li></ul>'), care_guide='<ul><li>Water regularly, keeping soil consistently moist.</li><li>Provide full sun.</li><li>Support with stakes or cages.</li></ul>'),
dict(name='Basil', climate=mediterranean, environment=greenhouse, dict(name='Basil', climate=mediterranean, environment=greenhouse,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=easy, growth_rate=moderate, care_difficulty=easy, growth_rate=moderate, section=herbs,
products=[soil, fert, light], description='A fragrant herb often grown in greenhouses or sunny windows.', products=[soil, fert, light], description='A fragrant herb often grown in greenhouses or sunny windows.',
care_guide='<ul><li>Water when soil surface is dry.</li><li>Needs at least 6 hours of sunlight daily.</li><li>Pinch off flowers to encourage leaf growth.</li></ul>'), care_guide='<ul><li>Water when soil surface is dry.</li><li>Needs at least 6 hours of sunlight daily.</li><li>Pinch off flowers to encourage leaf growth.</li></ul>'),
dict(name='Cactus', climate=arid, environment=indoor, dict(name='Cactus', climate=arid, environment=indoor,
light=bright_indirect, toxicity=non_toxic, size=medium, light=bright_indirect, toxicity=non_toxic, size=medium,
care_difficulty=easy, growth_rate=moderate, care_difficulty=easy, growth_rate=moderate, section=succulents,
products=[soil, light], description='A spiny plant adapted to dry, sunny conditions.', products=[soil, light], description='A spiny plant adapted to dry, sunny conditions.',
care_guide='<ul><li>Water sparingly, especially in winter.</li><li>Place in bright, direct sunlight.</li><li>Use cactus-specific soil mix.</li></ul>'), care_guide='<ul><li>Water sparingly, especially in winter.</li><li>Place in bright, direct sunlight.</li><li>Use cactus-specific soil mix.</li></ul>'),
] ]
@@ -1297,6 +1414,7 @@ def seed_db():
size_id=plant['size'].id if plant['size'] else None, size_id=plant['size'].id if plant['size'] else None,
care_difficulty_id=plant['care_difficulty'].id if plant['care_difficulty'] else None, care_difficulty_id=plant['care_difficulty'].id if plant['care_difficulty'] else None,
growth_rate_id=plant['growth_rate'].id if plant['growth_rate'] else None, growth_rate_id=plant['growth_rate'].id if plant['growth_rate'] else None,
section_id=plant['section'].id if plant['section'] else None,
products=','.join(str(p.id) for p in plant['products'] if p), products=','.join(str(p.id) for p in plant['products'] if p),
description=plant['description'], description=plant['description'],
care_guide=plant['care_guide'] care_guide=plant['care_guide']

View File

@@ -0,0 +1,34 @@
"""Add Section model and relationship to Plant
Revision ID: f746ca4a01f7
Revises: f83d55cc5aa7
Create Date: 2025-06-08 17:00:55.885897
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f746ca4a01f7'
down_revision = 'f83d55cc5aa7'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('plant', schema=None) as batch_op:
batch_op.add_column(sa.Column('section_id', sa.Integer(), nullable=True))
batch_op.create_foreign_key(None, 'section', ['section_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('plant', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.drop_column('section_id')
# ### end Alembic commands ###

View File

@@ -95,6 +95,15 @@
</div> </div>
<span class="badge-product text-xs font-semibold px-2 py-0.5 rounded">{{ product_count }}</span> <span class="badge-product text-xs font-semibold px-2 py-0.5 rounded">{{ product_count }}</span>
</a> </a>
<a href="{{ url_for('manage_sections') }}" class="nav-sections flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#e6f0f7]">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v18m9-9H3" />
</svg>
<span class="font-medium">Sections</span>
</div>
<span class="badge-section text-xs font-semibold px-2 py-0.5 rounded">{{ section_count }}</span>
</a>
</nav> </nav>
<!-- Only admin content is inside the background wrapper now --> <!-- Only admin content is inside the background wrapper now -->
<div class="bg-[#f5f7f2] shadow-lg rounded-xl p-8 mb-6"> <div class="bg-[#f5f7f2] shadow-lg rounded-xl p-8 mb-6">

View File

@@ -14,8 +14,8 @@
</div> </div>
<div> <div>
<label for="picture" class="block text-sm font-semibold text-gray-700 mb-1">Picture</label> <label for="picture" class="block text-sm font-semibold text-gray-700 mb-1">Picture</label>
<input type="file" name="picture" id="picture" <input type="file" name="picture" id="picture" accept="image/*"
class="mt-1 block w-full text-sm text-gray-700 border border-gray-300 rounded-lg px-3 py-2 bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"> class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>

View File

@@ -18,7 +18,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
{% if difficulty.icon %} {% if difficulty.icon %}
<div class="mt-2"> <div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span> <span class="text-xs text-gray-500">Current icon:</span>

View File

@@ -18,15 +18,13 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
{% if climate.icon %} {% if climate.icon %}
<img src="{{ url_for('static', filename='icons/' ~ climate.icon) }}" alt="Icon" class="w-8 h-8 mt-2"> <img src="{{ url_for('static', filename='icons/' ~ climate.icon) }}" alt="Icon" class="w-8 h-8 mt-2">
{% endif %} {% endif %}
</div> </div>
<button type="submit" <button type="submit" class="btn-main w-full">Save Changes</button>
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Save Changes
</button>
<a href="{{ url_for('manage_climates') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a> <a href="{{ url_for('manage_climates') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a>
</form> </form>
</div> </div>

View File

@@ -23,10 +23,7 @@
<img src="{{ url_for('static', filename='icons/' ~ environment.icon) }}" alt="Icon" class="w-8 h-8 mt-2"> <img src="{{ url_for('static', filename='icons/' ~ environment.icon) }}" alt="Icon" class="w-8 h-8 mt-2">
{% endif %} {% endif %}
</div> </div>
<button type="submit" <button type="submit" class="btn-main w-full">Save Changes</button>
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Save Changes
</button>
<a href="{{ url_for('manage_environments') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a> <a href="{{ url_for('manage_environments') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a>
</form> </form>
</div> </div>

View File

@@ -18,7 +18,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
{% if rate.icon %} {% if rate.icon %}
<div class="mt-2"> <div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span> <span class="text-xs text-gray-500">Current icon:</span>
@@ -26,7 +27,10 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<button type="submit" class="btn-main w-full">Save Changes</button> <button type="submit"
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Save Changes
</button>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -18,7 +18,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
{% if light.icon %} {% if light.icon %}
<div class="mt-2"> <div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span> <span class="text-xs text-gray-500">Current icon:</span>
@@ -26,7 +27,10 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<button type="submit" class="btn-main w-full">Save Changes</button> <button type="submit"
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Save Changes
</button>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -14,8 +14,8 @@
</div> </div>
<div> <div>
<label for="picture" class="block text-sm font-semibold text-gray-700 mb-1">Picture</label> <label for="picture" class="block text-sm font-semibold text-gray-700 mb-1">Picture</label>
<input type="file" name="picture" id="picture" <input type="file" name="picture" id="picture" accept="image/*"
class="mt-1 block w-full text-sm text-gray-700 border border-gray-300 rounded-lg px-3 py-2 bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"> class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
{% if plant.picture %} {% if plant.picture %}
<img src="{{ url_for('static', filename='uploads/' ~ plant.picture) }}" alt="{{ plant.name }}" class="w-24 h-24 object-cover rounded mt-2"> <img src="{{ url_for('static', filename='uploads/' ~ plant.picture) }}" alt="{{ plant.name }}" class="w-24 h-24 object-cover rounded mt-2">
{% endif %} {% endif %}
@@ -108,7 +108,10 @@
<div id="quill-care-guide" class="bg-white rounded border border-gray-300" style="min-height: 120px;">{{ plant.care_guide|safe }}</div> <div id="quill-care-guide" class="bg-white rounded border border-gray-300" style="min-height: 120px;">{{ plant.care_guide|safe }}</div>
<textarea name="care_guide" id="care_guide" style="display:none;">{{ plant.care_guide }}</textarea> <textarea name="care_guide" id="care_guide" style="display:none;">{{ plant.care_guide }}</textarea>
</div> </div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Save Changes</button> <button type="submit"
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Save Changes
</button>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -16,10 +16,7 @@
<textarea name="description" id="description" rows="3" <textarea name="description" id="description" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">{{ product.description }}</textarea> class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">{{ product.description }}</textarea>
</div> </div>
<button type="submit" <button type="submit" class="btn-main w-full">Save Changes</button>
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Save Changes
</button>
<a href="{{ url_for('manage_products') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a> <a href="{{ url_for('manage_products') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a>
</form> </form>
</div> </div>

View File

@@ -0,0 +1,34 @@
{% extends "admin_base.html" %}
{% block title %}Edit Section{% endblock %}
{% block admin_content %}
<div class="max-w-xl mx-auto bg-white p-8 rounded-lg shadow-md">
<h1 class="text-2xl font-bold mb-6">Edit Section</h1>
<form method="POST" enctype="multipart/form-data" class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-gray-700">Name</label>
<input type="text" name="name" id="name" value="{{ section.name }}" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
<textarea name="description" id="description" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">{{ section.description }}</textarea>
</div>
<div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon</label>
{% if section.icon %}
<div class="mb-2">
<img src="{{ url_for('static', filename='icons/' + section.icon) }}" alt="{{ section.name }}" class="h-8 w-8 object-contain">
</div>
{% endif %}
<input type="file" name="icon" id="icon" accept="image/*"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
<p class="mt-1 text-sm text-gray-500">Leave empty to keep the current icon</p>
</div>
<button type="submit" class="btn-main w-full">Save Changes</button>
<a href="{{ url_for('manage_sections') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a>
</form>
</div>
{% endblock %}

View File

@@ -18,7 +18,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
{% if size.icon %} {% if size.icon %}
<div class="mt-2"> <div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span> <span class="text-xs text-gray-500">Current icon:</span>
@@ -26,7 +27,10 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<button type="submit" class="btn-main w-full">Save Changes</button> <button type="submit"
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Save Changes
</button>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -18,7 +18,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
{% if toxicity.icon %} {% if toxicity.icon %}
<div class="mt-2"> <div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span> <span class="text-xs text-gray-500">Current icon:</span>
@@ -26,7 +27,10 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<button type="submit" class="btn-main w-full">Save Changes</button> <button type="submit"
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Save Changes
</button>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -20,7 +20,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Care Difficulty</button> <button type="submit" class="w-full btn-main text-lg font-semibold">Add Care Difficulty</button>
</form> </form>

View File

@@ -20,7 +20,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Climate</button> <button type="submit" class="w-full btn-main text-lg font-semibold">Add Climate</button>
</form> </form>

View File

@@ -20,7 +20,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Environment</button> <button type="submit" class="w-full btn-main text-lg font-semibold">Add Environment</button>
</form> </form>

View File

@@ -20,7 +20,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Growth Rate</button> <button type="submit" class="w-full btn-main text-lg font-semibold">Add Growth Rate</button>
</form> </form>

View File

@@ -20,7 +20,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Light Requirement</button> <button type="submit" class="w-full btn-main text-lg font-semibold">Add Light Requirement</button>
</form> </form>

View File

@@ -23,8 +23,8 @@
</div> </div>
<div> <div>
<label for="picture" class="block text-sm font-medium text-gray-700">Picture</label> <label for="picture" class="block text-sm font-medium text-gray-700">Picture</label>
<input type="file" name="picture" id="picture" <input type="file" name="picture" id="picture" accept="image/*"
class="mt-1 block w-full text-sm text-gray-700"> class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<div> <div>
<label for="climate_id" class="block text-sm font-medium text-gray-700">Climate</label> <label for="climate_id" class="block text-sm font-medium text-gray-700">Climate</label>

View File

@@ -0,0 +1,60 @@
{% extends "admin_base.html" %}
{% block title %}Manage Sections{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new section form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Section</h2>
<form method="POST" enctype="multipart/form-data" class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-gray-700">Name</label>
<input type="text" name="name" id="name" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
<textarea name="description" id="description" rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"></textarea>
</div>
<div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon</label>
<input type="file" name="icon" id="icon" accept="image/*"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Section</button>
</form>
</div>
<!-- List of existing sections -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Sections</h2>
<div class="space-y-4">
{% for section in sections %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div class="flex items-center">
{% if section.icon %}
<img src="{{ url_for('static', filename='icons/' + section.icon) }}" alt="{{ section.name }}" class="h-8 w-8 object-contain mr-3">
{% endif %}
<div>
<h3 class="font-bold">{{ section.name }}</h3>
{% if section.description %}
<p class="text-gray-600 mt-2">{{ section.description }}</p>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_section', section_id=section.id) }}" class="btn-edit">Edit</a>
<form method="POST" action="{{ url_for('delete_section', section_id=section.id) }}" onsubmit="return confirm('Are you sure you want to delete this section?');">
<button type="submit" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No sections added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -20,7 +20,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Size Category</button> <button type="submit" class="w-full btn-main text-lg font-semibold">Add Size Category</button>
</form> </form>

View File

@@ -20,7 +20,8 @@
</div> </div>
<div> <div>
<label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label> <label for="icon" class="block text-sm font-medium text-gray-700">Icon (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700"> <input type="file" name="icon" id="icon" accept=".svg"
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-[#6b8f71] file:text-white hover:file:bg-[#5a7b5f]">
</div> </div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Toxicity Level</button> <button type="submit" class="w-full btn-main text-lg font-semibold">Add Toxicity Level</button>
</form> </form>