Initial commit with project setup

This commit is contained in:
2025-05-22 19:23:32 +02:00
commit 589b082190
2703 changed files with 602922 additions and 0 deletions

104
templates/admin_base.html Normal file
View File

@@ -0,0 +1,104 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-6xl mx-auto">
<!-- Admin Header -->
<div class="flex items-center bg-gradient-to-r from-[#b7c7a3] to-[#6b8f71] rounded-xl p-7 mb-6 shadow-lg">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-14 h-14 text-[#4e6b50] mr-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 2C7 2 2 7 2 12c0 5 5 10 10 10s10-5 10-10c0-5-5-10-10-10zm0 0c0 4 4 8 8 8" />
</svg>
<div>
<h1 class="text-3xl font-bold text-[#3e5637] mb-1 tracking-tight">Admin Panel</h1>
<p class="text-[#4e6b50]">Welcome to Verpot Je Lot's plant care management dashboard.</p>
</div>
</div>
<!-- Navigation (now outside background wrapper) -->
<nav class="admin-panel-nav grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 mb-8">
<a href="{{ url_for('manage_plants') }}" class="nav-plants flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#e6ebe0]">
<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">Plants</span>
</div>
<span class="badge-plant text-xs font-semibold px-2 py-0.5 rounded">{{ plant_count }}</span>
</a>
<a href="{{ url_for('manage_environments') }}" class="nav-environments flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#e0f0eb]">
<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">Environments</span>
</div>
<span class="badge-env text-xs font-semibold px-2 py-0.5 rounded">{{ environment_count }}</span>
</a>
<a href="{{ url_for('manage_climates') }}" class="nav-climates flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#f7f2e6]">
<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">Climates</span>
</div>
<span class="badge-climate text-xs font-semibold px-2 py-0.5 rounded">{{ climate_count }}</span>
</a>
<a href="{{ url_for('manage_lights') }}" class="nav-lights flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#fffbe6]">
<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">Light Requirements</span>
</div>
<span class="badge-light text-xs font-semibold px-2 py-0.5 rounded">{{ light_count }}</span>
</a>
<a href="{{ url_for('manage_toxicities') }}" class="nav-toxicities flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#fff4e6]">
<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">Toxicity Levels</span>
</div>
<span class="badge-toxicity text-xs font-semibold px-2 py-0.5 rounded">{{ toxicity_count }}</span>
</a>
<a href="{{ url_for('manage_sizes') }}" class="nav-sizes flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#e6f7f5]">
<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">Size Categories</span>
</div>
<span class="badge-size text-xs font-semibold px-2 py-0.5 rounded">{{ size_count }}</span>
</a>
<a href="{{ url_for('manage_care_difficulties') }}" class="nav-difficulties flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#f3e6ff]">
<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">Care Difficulties</span>
</div>
<span class="badge-difficulty text-xs font-semibold px-2 py-0.5 rounded">{{ difficulty_count }}</span>
</a>
<a href="{{ url_for('manage_growth_rates') }}" class="nav-growth flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#f7fae6]">
<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">Growth Rates</span>
</div>
<span class="badge-rate text-xs font-semibold px-2 py-0.5 rounded">{{ rate_count }}</span>
</a>
<a href="{{ url_for('manage_products') }}" class="nav-products flex items-center justify-between p-4 rounded-lg transition-colors hover:bg-[#f7efe6]">
<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">Products</span>
</div>
<span class="badge-product text-xs font-semibold px-2 py-0.5 rounded">{{ product_count }}</span>
</a>
</nav>
<!-- Only admin content is inside the background wrapper now -->
<div class="bg-[#f5f7f2] shadow-lg rounded-xl p-8 mb-6">
{% block admin_content %}{% endblock %}
</div>
</div>
{% endblock %}

161
templates/base.html Normal file
View File

@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %} - Verpot Je Lot</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='styles/planty.css') }}">
</head>
<body class="min-h-screen bg-gradient-to-br from-[#e6ebe0] via-[#b7c7a3] to-[#6b8f71] bg-fixed">
<nav class="bg-[#f5f7f2]/95 shadow-lg backdrop-blur-md" id="main-navbar">
<div class="container mx-auto px-4">
<div class="flex justify-between">
<div class="flex space-x-7">
<div>
<a href="{{ url_for('home') }}" class="flex items-center py-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-7 h-7 text-[#6b8f71] mr-2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 2C7 2 2 7 2 12c0 5 5 10 10 10s10-5 10-10c0-5-5-10-10-10zm0 0c0 4 4 8 8 8" />
</svg>
<span class="font-bold text-[#3e5637] text-xl tracking-tight">Verpot Je Lot</span>
</a>
</div>
</div>
<div class="flex items-center space-x-3">
<!-- Admin links moved to footer -->
</div>
</div>
</div>
</nav>
<main class="container mx-auto px-4 py-8">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="mb-4 p-4 rounded-lg {% if category == 'success' %}bg-green-100 text-green-700{% else %}bg-red-100 text-red-700{% endif %} shadow">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
{% if request.endpoint == 'home' %}
<footer class="bg-[#f5f7f2]/95 text-[#3e5637] text-sm py-2 px-4 flex justify-between items-center shadow-t z-40 mt-8">
<span>Made by Kobe Amerijckx and Roos Amerijckx</span>
{% if is_logged_in %}
<a href="{{ url_for('manage_environments') }}" class="py-1 px-3 bg-[#6b8f71] hover:bg-[#4e6b50] text-white rounded-lg transition duration-200 shadow">Admin Panel</a>
<a href="{{ url_for('logout') }}" class="py-1 px-3 bg-[#e6ebe0] hover:bg-[#b7c7a3] text-[#3e5637] rounded-lg transition duration-200 ml-2">Logout</a>
{% else %}
<a href="{{ url_for('login') }}" class="py-1 px-3 bg-[#6b8f71] hover:bg-[#4e6b50] text-white rounded-lg transition duration-200 shadow">Admin Login</a>
{% endif %}
</footer>
{% endif %}
<!-- Delete Confirmation Modal -->
<div id="delete-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 max-w-md w-full mx-4 transform transition-all">
<div class="text-center mb-6">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-red-500 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<h3 class="text-xl font-bold text-gray-900 mb-2">Confirm Deletion</h3>
<p class="text-gray-600" id="delete-modal-message">Are you sure you want to delete this item?</p>
</div>
<div class="flex justify-end space-x-3">
<button onclick="closeDeleteModal()" class="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition duration-200">
Cancel
</button>
<form id="delete-form" method="POST" class="inline">
<button type="submit" class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition duration-200">
Delete
</button>
</form>
</div>
</div>
</div>
<script>
function showDeleteModal(formId, message) {
const modal = document.getElementById('delete-modal');
const form = document.getElementById(formId);
const modalForm = document.getElementById('delete-form');
const messageEl = document.getElementById('delete-modal-message');
// Set the message
messageEl.textContent = message || 'Are you sure you want to delete this item?';
// Set the form action
modalForm.action = form.action;
// Show the modal
modal.classList.remove('hidden');
modal.classList.add('flex');
// Add animation
const modalContent = modal.querySelector('.bg-white');
modalContent.classList.add('scale-95', 'opacity-0');
requestAnimationFrame(() => {
modalContent.classList.remove('scale-95', 'opacity-0');
});
}
function closeDeleteModal() {
const modal = document.getElementById('delete-modal');
const modalContent = modal.querySelector('.bg-white');
// Add animation
modalContent.classList.add('scale-95', 'opacity-0');
// Hide after animation
setTimeout(() => {
modal.classList.remove('flex');
modal.classList.add('hidden');
modalContent.classList.remove('scale-95', 'opacity-0');
}, 200);
}
// Close modal when clicking outside
document.getElementById('delete-modal').addEventListener('click', function(e) {
if (e.target === this) {
closeDeleteModal();
}
});
// Animate plant cards on scroll
document.addEventListener('DOMContentLoaded', function() {
const cards = document.querySelectorAll('.plant-card');
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.remove('opacity-0', 'translate-y-8');
entry.target.classList.add('opacity-100', 'translate-y-0');
obs.unobserve(entry.target);
}
});
}, { threshold: 0.15 });
cards.forEach(card => observer.observe(card));
} else {
// Fallback: show all
cards.forEach(card => card.classList.remove('opacity-0', 'translate-y-8'));
}
// Localize all .local-date elements
document.querySelectorAll('.local-date').forEach(function(el) {
const date = new Date(el.dataset.date);
el.textContent = date.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric' }) +
' ' + date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
});
});
</script>
<style>
.plant-card {
will-change: opacity, transform;
}
</style>
</body>
</html>

106
templates/create_plant.html Normal file
View File

@@ -0,0 +1,106 @@
{% extends "admin_base.html" %}
{% block title %}New Plant{% endblock %}
{% block admin_content %}
<div class="max-w-2xl mx-auto">
<h1 class="text-3xl font-bold mb-6 text-center">Add New Plant</h1>
<div class="bg-white shadow-lg rounded-xl p-8">
<form method="POST" enctype="multipart/form-data" class="space-y-6">
<div>
<label for="name" class="block text-sm font-semibold text-gray-700 mb-1">Name</label>
<input type="text" name="name" id="name" required
class="mt-1 block w-full rounded-lg border border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2">
</div>
<div>
<label for="picture" class="block text-sm font-semibold text-gray-700 mb-1">Picture</label>
<input type="file" name="picture" id="picture"
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">
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="climate_id" class="block text-sm font-medium text-gray-700">Climate</label>
<select name="climate_id" id="climate_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select a climate</option>
{% for climate in climates %}
<option value="{{ climate.id }}">{{ climate.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="environment_id" class="block text-sm font-medium text-gray-700">Environment</label>
<select name="environment_id" id="environment_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select an environment</option>
{% for environment in environments %}
<option value="{{ environment.id }}">{{ environment.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="light_id" class="block text-sm font-medium text-gray-700">Light</label>
<select name="light_id" id="light_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select light requirement</option>
{% for light in lights %}
<option value="{{ light.id }}">{{ light.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="toxicity_id" class="block text-sm font-medium text-gray-700">Toxicity</label>
<select name="toxicity_id" id="toxicity_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select toxicity level</option>
{% for toxicity in toxicities %}
<option value="{{ toxicity.id }}">{{ toxicity.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="size_id" class="block text-sm font-medium text-gray-700">Size</label>
<select name="size_id" id="size_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select size category</option>
{% for size in sizes %}
<option value="{{ size.id }}">{{ size.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="care_difficulty_id" class="block text-sm font-medium text-gray-700">Care Difficulty</label>
<select name="care_difficulty_id" id="care_difficulty_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select difficulty level</option>
{% for difficulty in difficulties %}
<option value="{{ difficulty.id }}">{{ difficulty.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="growth_rate_id" class="block text-sm font-medium text-gray-700">Growth Rate</label>
<select name="growth_rate_id" id="growth_rate_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select growth rate</option>
{% for rate in growth_rates %}
<option value="{{ rate.id }}">{{ rate.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="md:col-span-2">
<label for="product_ids" class="block text-sm font-medium text-gray-700">Products</label>
<select name="product_ids" id="product_ids" multiple required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
{% for product in products %}
<option value="{{ product.id }}">{{ product.name }}</option>
{% endfor %}
</select>
<p class="mt-1 text-sm text-gray-500">Hold Ctrl (or Cmd on Mac) to select multiple products</p>
</div>
<div class="md:col-span-2">
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
<textarea name="description" id="description" rows="6" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"></textarea>
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Plant</button>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "admin_base.html" %}
{% block title %}Edit Care Difficulty{% endblock %}
{% block admin_content %}
<div class="max-w-xl mx-auto bg-gray-50 p-6 rounded-lg">
<h2 class="text-2xl font-bold mb-4">Edit Care Difficulty</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" value="{{ difficulty.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">{{ difficulty.description }}</textarea>
</div>
<div>
<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">
{% if difficulty.icon %}
<div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span>
<img src="{{ url_for('static', filename='icons/' ~ difficulty.icon) }}" alt="Icon" class="w-8 h-8 inline-block align-middle">
</div>
{% endif %}
</div>
<button type="submit" class="btn-main w-full">Save Changes</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,33 @@
{% extends "admin_base.html" %}
{% block title %}Edit Climate{% 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 Climate</h1>
<form method="POST" 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="{{ climate.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">{{ climate.description }}</textarea>
</div>
<div>
<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">
{% if climate.icon %}
<img src="{{ url_for('static', filename='icons/' ~ climate.icon) }}" alt="Icon" class="w-8 h-8 mt-2">
{% endif %}
</div>
<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>
<a href="{{ url_for('manage_climates') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,33 @@
{% extends "admin_base.html" %}
{% block title %}Edit Environment{% 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 Environment</h1>
<form method="POST" 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="{{ environment.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">{{ environment.description }}</textarea>
</div>
<div>
<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">
{% if environment.icon %}
<img src="{{ url_for('static', filename='icons/' ~ environment.icon) }}" alt="Icon" class="w-8 h-8 mt-2">
{% endif %}
</div>
<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>
<a href="{{ url_for('manage_environments') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "admin_base.html" %}
{% block title %}Edit Growth Rate{% endblock %}
{% block admin_content %}
<div class="max-w-xl mx-auto bg-gray-50 p-6 rounded-lg">
<h2 class="text-2xl font-bold mb-4">Edit Growth Rate</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" value="{{ rate.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">{{ rate.description }}</textarea>
</div>
<div>
<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">
{% if rate.icon %}
<div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span>
<img src="{{ url_for('static', filename='icons/' ~ rate.icon) }}" alt="Icon" class="w-8 h-8 inline-block align-middle">
</div>
{% endif %}
</div>
<button type="submit" class="btn-main w-full">Save Changes</button>
</form>
</div>
{% endblock %}

32
templates/edit_light.html Normal file
View File

@@ -0,0 +1,32 @@
{% extends "admin_base.html" %}
{% block title %}Edit Light Requirement{% endblock %}
{% block admin_content %}
<div class="max-w-xl mx-auto bg-gray-50 p-6 rounded-lg">
<h2 class="text-2xl font-bold mb-4">Edit Light Requirement</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" value="{{ light.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">{{ light.description }}</textarea>
</div>
<div>
<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">
{% if light.icon %}
<div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span>
<img src="{{ url_for('static', filename='icons/' ~ light.icon) }}" alt="Icon" class="w-8 h-8 inline-block align-middle">
</div>
{% endif %}
</div>
<button type="submit" class="btn-main w-full">Save Changes</button>
</form>
</div>
{% endblock %}

128
templates/edit_plant.html Normal file
View File

@@ -0,0 +1,128 @@
{% extends "admin_base.html" %}
{% block title %}Edit Plant{% endblock %}
{% block admin_content %}
<div class="max-w-2xl mx-auto">
<h1 class="text-3xl font-bold mb-6 text-center">Edit Plant</h1>
<div class="bg-white shadow-lg rounded-xl p-8">
<form method="POST" enctype="multipart/form-data" class="space-y-6">
<div>
<label for="name" class="block text-sm font-semibold text-gray-700 mb-1">Name</label>
<input type="text" name="name" id="name" value="{{ plant.name }}" required
class="mt-1 block w-full rounded-lg border border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2">
</div>
<div>
<label for="picture" class="block text-sm font-semibold text-gray-700 mb-1">Picture</label>
<input type="file" name="picture" id="picture"
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">
{% 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">
{% endif %}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="climate_id" class="block text-sm font-medium text-gray-700">Climate</label>
<select name="climate_id" id="climate_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select a climate</option>
{% for climate in climates %}
<option value="{{ climate.id }}" {% if plant.climate_id == climate.id %}selected{% endif %}>{{ climate.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="environment_id" class="block text-sm font-medium text-gray-700">Environment</label>
<select name="environment_id" id="environment_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select an environment</option>
{% for environment in environments %}
<option value="{{ environment.id }}" {% if plant.environment_id == environment.id %}selected{% endif %}>{{ environment.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="light_id" class="block text-sm font-medium text-gray-700">Light</label>
<select name="light_id" id="light_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select light requirement</option>
{% for light in lights %}
<option value="{{ light.id }}" {% if plant.light_id == light.id %}selected{% endif %}>{{ light.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="toxicity_id" class="block text-sm font-medium text-gray-700">Toxicity</label>
<select name="toxicity_id" id="toxicity_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select toxicity level</option>
{% for toxicity in toxicities %}
<option value="{{ toxicity.id }}" {% if plant.toxicity_id == toxicity.id %}selected{% endif %}>{{ toxicity.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="size_id" class="block text-sm font-medium text-gray-700">Size</label>
<select name="size_id" id="size_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select size category</option>
{% for size in sizes %}
<option value="{{ size.id }}" {% if plant.size_id == size.id %}selected{% endif %}>{{ size.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="care_difficulty_id" class="block text-sm font-medium text-gray-700">Care Difficulty</label>
<select name="care_difficulty_id" id="care_difficulty_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select difficulty level</option>
{% for difficulty in difficulties %}
<option value="{{ difficulty.id }}" {% if plant.care_difficulty_id == difficulty.id %}selected{% endif %}>{{ difficulty.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="growth_rate_id" class="block text-sm font-medium text-gray-700">Growth Rate</label>
<select name="growth_rate_id" id="growth_rate_id" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select growth rate</option>
{% for rate in growth_rates %}
<option value="{{ rate.id }}" {% if plant.growth_rate_id == rate.id %}selected{% endif %}>{{ rate.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Products</label>
<div class="flex flex-wrap gap-4">
{% for product in products %}
<label class="inline-flex items-center">
<input type="checkbox" name="product_ids" value="{{ product.id }}" {% if product.id|string in selected_products %}checked{% endif %} class="form-checkbox rounded text-[#6b8f71] focus:ring-[#6b8f71]">
<span class="ml-2">{{ product.name }}</span>
</label>
{% endfor %}
</div>
</div>
<div class="md:col-span-2">
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
<textarea name="description" id="description" rows="6" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">{{ plant.description }}</textarea>
</div>
<div class="md:col-span-2">
<label for="care_guide" class="block text-sm font-medium text-gray-700">Care Guide</label>
<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>
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Save Changes</button>
</form>
</div>
</div>
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script>
var quill = new Quill('#quill-care-guide', { theme: 'snow' });
// Set initial content from textarea if not already set
var careGuideTextarea = document.getElementById('care_guide');
if (careGuideTextarea.value) {
quill.root.innerHTML = careGuideTextarea.value;
}
document.querySelector('form').onsubmit = function() {
careGuideTextarea.value = quill.root.innerHTML;
};
</script>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "admin_base.html" %}
{% block title %}Edit Product{% 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 Product</h1>
<form method="POST" 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="{{ product.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">{{ product.description }}</textarea>
</div>
<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>
<a href="{{ url_for('manage_products') }}" class="block text-center mt-4 text-gray-500 hover:text-gray-700">Cancel</a>
</form>
</div>
{% endblock %}

32
templates/edit_size.html Normal file
View File

@@ -0,0 +1,32 @@
{% extends "admin_base.html" %}
{% block title %}Edit Size Category{% endblock %}
{% block admin_content %}
<div class="max-w-xl mx-auto bg-gray-50 p-6 rounded-lg">
<h2 class="text-2xl font-bold mb-4">Edit Size Category</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" value="{{ size.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">{{ size.description }}</textarea>
</div>
<div>
<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">
{% if size.icon %}
<div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span>
<img src="{{ url_for('static', filename='icons/' ~ size.icon) }}" alt="Icon" class="w-8 h-8 inline-block align-middle">
</div>
{% endif %}
</div>
<button type="submit" class="btn-main w-full">Save Changes</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "admin_base.html" %}
{% block title %}Edit Toxicity Level{% endblock %}
{% block admin_content %}
<div class="max-w-xl mx-auto bg-gray-50 p-6 rounded-lg">
<h2 class="text-2xl font-bold mb-4">Edit Toxicity Level</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" value="{{ toxicity.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">{{ toxicity.description }}</textarea>
</div>
<div>
<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">
{% if toxicity.icon %}
<div class="mt-2">
<span class="text-xs text-gray-500">Current icon:</span>
<img src="{{ url_for('static', filename='icons/' ~ toxicity.icon) }}" alt="Icon" class="w-8 h-8 inline-block align-middle">
</div>
{% endif %}
</div>
<button type="submit" class="btn-main w-full">Save Changes</button>
</form>
</div>
{% endblock %}

173
templates/home.html Normal file
View File

@@ -0,0 +1,173 @@
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<button id="open-filter-menu" class="sm:hidden btn-main mb-4 w-full">Filter</button>
<div id="filter-modal" class="fixed inset-0 bg-black bg-opacity-40 z-50 flex items-center justify-center hidden sm:static sm:bg-transparent sm:z-auto sm:flex sm:items-start sm:justify-start">
<div class="bg-white rounded-xl p-6 w-full max-w-md mx-auto relative sm:bg-transparent sm:p-0 sm:w-auto sm:max-w-none sm:mx-0">
<button id="close-filter-menu" class="sm:hidden absolute top-4 right-4 text-2xl">&times;</button>
<form id="plant-filter-form" method="get"
class="flex flex-col sm:flex-row flex-wrap gap-4 items-center p-0 w-full mb-8">
<input type="text" id="search" name="search" value="{{ search }}" placeholder="Search..." class="rounded-lg border border-gray-300 px-3 py-1 text-sm focus:border-[#6b8f71] focus:ring-[#6b8f71]" style="min-width: 160px;" autocomplete="off">
<select id="climate" name="climate" class="rounded-lg border border-gray-300 px-3 py-1 text-sm focus:border-[#6b8f71] focus:ring-[#6b8f71]">
<option value="">All Climates</option>
{% for climate in climates %}
<option value="{{ climate.id }}" {% if selected_climate == climate.id|string %}selected{% endif %}>{{ climate.name }}</option>
{% endfor %}
</select>
<select id="environment" name="environment" class="rounded-lg border border-gray-300 px-3 py-1 text-sm focus:border-[#6b8f71] focus:ring-[#6b8f71]">
<option value="">All Environments</option>
{% for environment in environments %}
<option value="{{ environment.id }}" {% if selected_environment == environment.id|string %}selected{% endif %}>{{ environment.name }}</option>
{% endfor %}
</select>
<select id="light" name="light" class="rounded-lg border border-gray-300 px-3 py-1 text-sm focus:border-[#6b8f71] focus:ring-[#6b8f71]">
<option value="">All Light</option>
{% for light in lights %}
<option value="{{ light.id }}" {% if selected_light == light.id|string %}selected{% endif %}>{{ light.name }}</option>
{% endfor %}
</select>
<select id="toxicity" name="toxicity" class="rounded-lg border border-gray-300 px-3 py-1 text-sm focus:border-[#6b8f71] focus:ring-[#6b8f71]">
<option value="">All Toxicity</option>
{% for toxicity in toxicities %}
<option value="{{ toxicity.id }}" {% if selected_toxicity == toxicity.id|string %}selected{% endif %}>{{ toxicity.name }}</option>
{% endfor %}
</select>
<select id="size" name="size" class="rounded-lg border border-gray-300 px-3 py-1 text-sm focus:border-[#6b8f71] focus:ring-[#6b8f71]">
<option value="">All Sizes</option>
{% for size in sizes %}
<option value="{{ size.id }}" {% if selected_size == size.id|string %}selected{% endif %}>{{ size.name }}</option>
{% endfor %}
</select>
<select id="care_difficulty" name="care_difficulty" class="rounded-lg border border-gray-300 px-3 py-1 text-sm focus:border-[#6b8f71] focus:ring-[#6b8f71]">
<option value="">All Care Difficulty</option>
{% for difficulty in difficulties %}
<option value="{{ difficulty.id }}" {% if selected_care_difficulty == difficulty.id|string %}selected{% endif %}>{{ difficulty.name }}</option>
{% endfor %}
</select>
<select id="growth_rate" name="growth_rate" class="rounded-lg border border-gray-300 px-3 py-1 text-sm focus:border-[#6b8f71] focus:ring-[#6b8f71]">
<option value="">All Growth Rates</option>
{% for rate in growth_rates %}
<option value="{{ rate.id }}" {% if selected_growth_rate == rate.id|string %}selected{% endif %}>{{ rate.name }}</option>
{% endfor %}
</select>
<a href="{{ url_for('home') }}" class="btn-secondary px-4 py-1 text-sm font-semibold ml-2 sm:ml-0">Clear</a>
</form>
</div>
</div>
<script>
// Debounce function
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const form = document.getElementById('plant-filter-form');
const searchInput = document.getElementById('search');
const selects = [document.getElementById('climate'), document.getElementById('environment'), document.getElementById('light'), document.getElementById('toxicity'), document.getElementById('size'), document.getElementById('care_difficulty'), document.getElementById('growth_rate')];
// Auto-submit on select change
selects.forEach(sel => sel.addEventListener('change', () => form.submit()));
// Debounced auto-submit on search
searchInput.addEventListener('input', debounce(() => form.submit(), 400));
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const openBtn = document.getElementById('open-filter-menu');
const closeBtn = document.getElementById('close-filter-menu');
const modal = document.getElementById('filter-modal');
if (openBtn && modal) {
openBtn.addEventListener('click', () => modal.classList.remove('hidden'));
}
if (closeBtn && modal) {
closeBtn.addEventListener('click', () => modal.classList.add('hidden'));
}
if (modal) {
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.classList.add('hidden');
});
}
});
</script>
<div class="masonry md:columns-2 xl:columns-3 gap-8">
{% for plant in plants %}
<a href="{{ url_for('plant', plant_id=plant.id) }}" class="block group focus:outline-none focus:ring-2 focus:ring-[#6b8f71] rounded-2xl">
<article class="plant-card opacity-0 translate-y-8 transition-all duration-700 bg-white/90 shadow-xl rounded-2xl p-6 flex flex-col hover:shadow-2xl hover:scale-[1.02] group-hover:shadow-2xl group-hover:scale-[1.02] cursor-pointer">
{% if plant.picture %}
<img src="{{ url_for('static', filename='uploads/' ~ plant.picture) }}" alt="{{ plant.name }}" class="w-full h-64 object-cover rounded-xl shadow-md border-2 border-[#e6ebe0] mb-4">
{% else %}
<img src="{{ url_for('static', filename='images/placeholder-plant.svg') }}" alt="No image available" class="w-full h-64 object-cover rounded-xl shadow-md border-2 border-[#e6ebe0] mb-4 opacity-50">
{% endif %}
<div class="w-full flex flex-col gap-2">
<h2 class="text-2xl font-bold mb-1 text-[#4e6b50] group-hover:text-[#3e5637] transition">{{ plant.name }}</h2>
<div class="flex flex-wrap gap-2 mb-1">
{% if plant.climate %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#d0e7d2] text-[#3e5637] text-xs font-semibold">
{% if plant.climate_icon %}
<img src="{{ url_for('static', filename='icons/' ~ plant.climate_icon) }}" alt="Climate icon" class="w-4 h-4 mr-1 inline-block align-middle" />
{% endif %}
{{ plant.climate }}
</span>
{% endif %}
{% if plant.environment %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#d0e7d2] text-[#3e5637] text-xs font-semibold">
{% if plant.environment_icon %}
<img src="{{ url_for('static', filename='icons/' ~ plant.environment_icon) }}" alt="Environment icon" class="w-4 h-4 mr-1 inline-block align-middle" />
{% endif %}
{{ plant.environment }}
</span>
{% endif %}
{% if plant.light %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#d0e7d2] text-[#3e5637] text-xs font-semibold">
{% if plant.light_icon %}
<img src="{{ url_for('static', filename='icons/' ~ plant.light_icon) }}" alt="Light icon" class="w-4 h-4 mr-1 inline-block align-middle" />
{% endif %}
{{ plant.light }}
</span>
{% endif %}
{% if plant.toxicity %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#d0e7d2] text-[#3e5637] text-xs font-semibold">
{% if plant.toxicity_icon %}
<img src="{{ url_for('static', filename='icons/' ~ plant.toxicity_icon) }}" alt="Toxicity icon" class="w-4 h-4 mr-1 inline-block align-middle" />
{% endif %}
{{ plant.toxicity }}
</span>
{% endif %}
</div>
<div class="flex flex-wrap gap-2 text-xs mb-1">
{% if plant.size %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#e6ebe0] text-[#3e5637] font-semibold">
{% if plant.size_icon %}
<img src="{{ url_for('static', filename='icons/' ~ plant.size_icon) }}" alt="Size icon" class="w-4 h-4 mr-1 inline-block align-middle" />
{% endif %}
{{ plant.size }}
</span>
{% endif %}
{% if plant.care_difficulty %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#e6ebe0] text-[#3e5637] font-semibold">
{% if plant.care_difficulty_icon %}
<img src="{{ url_for('static', filename='icons/' ~ plant.care_difficulty_icon) }}" alt="Care difficulty icon" class="w-4 h-4 mr-1 inline-block align-middle" />
{% endif %}
{{ plant.care_difficulty }}
</span>
{% endif %}
{% if plant.growth_rate %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#e6ebe0] text-[#3e5637] font-semibold">
{% if plant.growth_rate_icon %}
<img src="{{ url_for('static', filename='icons/' ~ plant.growth_rate_icon) }}" alt="Growth rate icon" class="w-4 h-4 mr-1 inline-block align-middle" />
{% endif %}
{{ plant.growth_rate }}
</span>
{% endif %}
</div>
<p class="text-[#4e6b50] mt-2 text-sm">{{ plant.description[:120] }}{% if plant.description and plant.description|length > 120 %}...{% endif %}</p>
<div class="text-xs text-[#6b8f71] mt-2">Added on <span class="local-date" data-date="{{ plant.date_added.isoformat() }}"></span></div>
</div>
</article>
</a>
{% endfor %}
</div>
{% endblock %}

20
templates/login.html Normal file
View File

@@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block title %}Admin Login{% endblock %}
{% block content %}
<div class="max-w-md mx-auto card">
<h1 class="text-2xl font-bold mb-6 text-center text-[#3e5637]">Admin Login</h1>
<form method="POST" class="space-y-4">
<div>
<label for="username" class="block text-sm font-medium text-[#3e5637]">Username</label>
<input type="text" name="username" id="username" required class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-[#6b8f71] focus:ring-[#6b8f71]">
</div>
<div>
<label for="password" class="block text-sm font-medium text-[#3e5637]">Password</label>
<input type="password" name="password" id="password" required class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-[#6b8f71] focus:ring-[#6b8f71]">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Login</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,139 @@
{% extends "admin_base.html" %}
{% block title %}Manage Plant Attributes{% endblock %}
{% block admin_content %}
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold mb-6 text-center">Manage Plant Attributes</h1>
<!-- Light Requirements -->
<div class="bg-white shadow-lg rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold mb-4 text-[#4e6b50]">Light Requirements</h2>
<form method="POST" action="{{ url_for('manage_light') }}" class="space-y-4">
<div class="flex gap-4">
<input type="text" name="name" placeholder="New light requirement" required
class="flex-1 rounded-lg border border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2">
<button type="submit" class="btn-main px-6">Add</button>
</div>
</form>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for light in lights %}
<div class="flex items-center justify-between p-3 bg-[#f8f9fa] rounded-lg">
<span class="text-[#4e6b50]">{{ light.name }}</span>
<form method="POST" action="{{ url_for('delete_light', light_id=light.id) }}" class="inline">
<button type="submit" class="text-red-600 hover:text-red-800">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</div>
{% endfor %}
</div>
</div>
<!-- Toxicity Levels -->
<div class="bg-white shadow-lg rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold mb-4 text-[#4e6b50]">Toxicity Levels</h2>
<form method="POST" action="{{ url_for('manage_toxicity') }}" class="space-y-4">
<div class="flex gap-4">
<input type="text" name="name" placeholder="New toxicity level" required
class="flex-1 rounded-lg border border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2">
<button type="submit" class="btn-main px-6">Add</button>
</div>
</form>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for toxicity in toxicities %}
<div class="flex items-center justify-between p-3 bg-[#f8f9fa] rounded-lg">
<span class="text-[#4e6b50]">{{ toxicity.name }}</span>
<form method="POST" action="{{ url_for('delete_toxicity', toxicity_id=toxicity.id) }}" class="inline">
<button type="submit" class="text-red-600 hover:text-red-800">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</div>
{% endfor %}
</div>
</div>
<!-- Size Categories -->
<div class="bg-white shadow-lg rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold mb-4 text-[#4e6b50]">Size Categories</h2>
<form method="POST" action="{{ url_for('manage_size') }}" class="space-y-4">
<div class="flex gap-4">
<input type="text" name="name" placeholder="New size category" required
class="flex-1 rounded-lg border border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2">
<button type="submit" class="btn-main px-6">Add</button>
</div>
</form>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for size in sizes %}
<div class="flex items-center justify-between p-3 bg-[#f8f9fa] rounded-lg">
<span class="text-[#4e6b50]">{{ size.name }}</span>
<form method="POST" action="{{ url_for('delete_size', size_id=size.id) }}" class="inline">
<button type="submit" class="text-red-600 hover:text-red-800">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</div>
{% endfor %}
</div>
</div>
<!-- Care Difficulty Levels -->
<div class="bg-white shadow-lg rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold mb-4 text-[#4e6b50]">Care Difficulty Levels</h2>
<form method="POST" action="{{ url_for('manage_care_difficulty') }}" class="space-y-4">
<div class="flex gap-4">
<input type="text" name="name" placeholder="New difficulty level" required
class="flex-1 rounded-lg border border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2">
<button type="submit" class="btn-main px-6">Add</button>
</div>
</form>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for difficulty in difficulties %}
<div class="flex items-center justify-between p-3 bg-[#f8f9fa] rounded-lg">
<span class="text-[#4e6b50]">{{ difficulty.name }}</span>
<form method="POST" action="{{ url_for('delete_care_difficulty', difficulty_id=difficulty.id) }}" class="inline">
<button type="submit" class="text-red-600 hover:text-red-800">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</div>
{% endfor %}
</div>
</div>
<!-- Growth Rate Categories -->
<div class="bg-white shadow-lg rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold mb-4 text-[#4e6b50]">Growth Rate Categories</h2>
<form method="POST" action="{{ url_for('manage_growth_rate') }}" class="space-y-4">
<div class="flex gap-4">
<input type="text" name="name" placeholder="New growth rate category" required
class="flex-1 rounded-lg border border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2">
<button type="submit" class="btn-main px-6">Add</button>
</div>
</form>
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for rate in growth_rates %}
<div class="flex items-center justify-between p-3 bg-[#f8f9fa] rounded-lg">
<span class="text-[#4e6b50]">{{ rate.name }}</span>
<form method="POST" action="{{ url_for('delete_growth_rate', rate_id=rate.id) }}" class="inline">
<button type="submit" class="text-red-600 hover:text-red-800">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "admin_base.html" %}
{% block title %}Manage Care Difficulties{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new care difficulty form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Care Difficulty</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 (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Care Difficulty</button>
</form>
</div>
<!-- List of existing care difficulties -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Care Difficulties</h2>
<div class="space-y-4">
{% for difficulty in difficulties %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div class="flex items-center gap-3">
{% if difficulty.icon %}
<img src="{{ url_for('static', filename='icons/' ~ difficulty.icon) }}" alt="Icon" class="w-8 h-8 inline-block">
{% endif %}
<div>
<h3 class="font-bold">{{ difficulty.name }}</h3>
{% if difficulty.description %}
<p class="text-gray-600 mt-2">{{ difficulty.description }}</p>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_care_difficulty', difficulty_id=difficulty.id) }}" class="btn-edit">Edit</a>
<form id="delete-difficulty-{{ difficulty.id }}" method="POST" action="{{ url_for('delete_care_difficulty', difficulty_id=difficulty.id) }}" style="display:inline;">
<button type="button" onclick="showDeleteModal('delete-difficulty-{{ difficulty.id }}', 'Are you sure you want to delete the care difficulty {{ difficulty.name }}?')" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No care difficulties added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "admin_base.html" %}
{% block title %}Manage Climates{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new climate form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Climate</h2>
<form method="POST" 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 (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Climate</button>
</form>
</div>
<!-- List of existing climates -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Climates</h2>
<div class="space-y-4">
{% for climate in climates %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div class="flex items-center gap-3">
{% if climate.icon %}
<img src="{{ url_for('static', filename='icons/' ~ climate.icon) }}" alt="Icon" class="w-8 h-8 inline-block">
{% endif %}
<div>
<h3 class="font-bold">{{ climate.name }}</h3>
{% if climate.description %}
<p class="text-gray-600 mt-2">{{ climate.description }}</p>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_climate', climate_id=climate.id) }}" class="btn-edit">Edit</a>
<form id="delete-climate-{{ climate.id }}" method="POST" action="{{ url_for('delete_climate', climate_id=climate.id) }}" style="display:inline;">
<button type="button" onclick="showDeleteModal('delete-climate-{{ climate.id }}', 'Are you sure you want to delete the climate {{ climate.name }}?')" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No climates added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "admin_base.html" %}
{% block title %}Manage Environments{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new environment form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Environment</h2>
<form method="POST" 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 (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Environment</button>
</form>
</div>
<!-- List of existing environments -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Environments</h2>
<div class="space-y-4">
{% for environment in environments %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div class="flex items-center gap-3">
{% if environment.icon %}
<img src="{{ url_for('static', filename='icons/' ~ environment.icon) }}" alt="Icon" class="w-8 h-8 inline-block">
{% endif %}
<div>
<h3 class="font-bold">{{ environment.name }}</h3>
{% if environment.description %}
<p class="text-gray-600 mt-2">{{ environment.description }}</p>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_environment', environment_id=environment.id) }}" class="btn-edit">Edit</a>
<form id="delete-environment-{{ environment.id }}" method="POST" action="{{ url_for('delete_environment', environment_id=environment.id) }}" style="display:inline;">
<button type="button" onclick="showDeleteModal('delete-environment-{{ environment.id }}', 'Are you sure you want to delete the environment {{ environment.name }}?')" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No environments added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "admin_base.html" %}
{% block title %}Manage Growth Rates{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new growth rate form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Growth Rate</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 (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Growth Rate</button>
</form>
</div>
<!-- List of existing growth rates -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Growth Rates</h2>
<div class="space-y-4">
{% for rate in rates %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div class="flex items-center gap-3">
{% if rate.icon %}
<img src="{{ url_for('static', filename='icons/' ~ rate.icon) }}" alt="Icon" class="w-8 h-8 inline-block">
{% endif %}
<div>
<h3 class="font-bold">{{ rate.name }}</h3>
{% if rate.description %}
<p class="text-gray-600 mt-2">{{ rate.description }}</p>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_growth_rate', rate_id=rate.id) }}" class="btn-edit">Edit</a>
<form id="delete-rate-{{ rate.id }}" method="POST" action="{{ url_for('delete_growth_rate', rate_id=rate.id) }}" style="display:inline;">
<button type="button" onclick="showDeleteModal('delete-rate-{{ rate.id }}', 'Are you sure you want to delete the growth rate {{ rate.name }}?')" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No growth rates added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "admin_base.html" %}
{% block title %}Manage Light Requirements{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new light requirement form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Light Requirement</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 (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Light Requirement</button>
</form>
</div>
<!-- List of existing light requirements -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Light Requirements</h2>
<div class="space-y-4">
{% for light in lights %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div class="flex items-center gap-3">
{% if light.icon %}
<img src="{{ url_for('static', filename='icons/' ~ light.icon) }}" alt="Icon" class="w-8 h-8 inline-block">
{% endif %}
<div>
<h3 class="font-bold">{{ light.name }}</h3>
{% if light.description %}
<p class="text-gray-600 mt-2">{{ light.description }}</p>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_light', light_id=light.id) }}" class="btn-edit">Edit</a>
<form id="delete-light-{{ light.id }}" method="POST" action="{{ url_for('delete_light', light_id=light.id) }}" style="display:inline;">
<button type="button" onclick="showDeleteModal('delete-light-{{ light.id }}', 'Are you sure you want to delete the light requirement {{ light.name }}?')" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No light requirements added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,170 @@
{% extends "admin_base.html" %}
{% block title %}Plants{% endblock %}
{% block admin_content %}
<div class="bg-white rounded-lg shadow p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">All Plants</h2>
<button id="show-add-plant" class="btn-main px-6 py-2 font-semibold">Add Plant</button>
</div>
<div id="add-plant-form-card" class="hidden mb-8">
<div class="bg-gray-50 rounded-lg shadow p-6 max-w-2xl mx-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">Add New Plant</h3>
<button type="button" id="close-add-plant" class="text-gray-500 hover:text-red-500 text-2xl font-bold leading-none">&times;</button>
</div>
<form id="add-plant-form" method="POST" enctype="multipart/form-data" class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-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="picture" class="block text-sm font-medium text-gray-700">Picture</label>
<input type="file" name="picture" id="picture"
class="mt-1 block w-full text-sm text-gray-700">
</div>
<div>
<label for="climate_id" class="block text-sm font-medium text-gray-700">Climate</label>
<select name="climate_id" id="climate_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select a climate</option>
{% for climate in climates %}
<option value="{{ climate.id }}">{{ climate.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="environment_id" class="block text-sm font-medium text-gray-700">Environment</label>
<select name="environment_id" id="environment_id" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select an environment</option>
{% for environment in environments %}
<option value="{{ environment.id }}">{{ environment.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="light" class="block text-sm font-medium text-gray-700">Light</label>
<select name="light" id="light" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select light</option>
<option value="Full Sun">Full Sun</option>
<option value="Partial Shade">Partial Shade</option>
<option value="Low Light">Low Light</option>
</select>
</div>
<div>
<label for="toxicity" class="block text-sm font-medium text-gray-700">Toxicity</label>
<select name="toxicity" id="toxicity" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select toxicity</option>
<option value="Pet Safe">Pet Safe</option>
<option value="Toxic to Pets">Toxic to Pets</option>
<option value="Unknown">Unknown</option>
</select>
</div>
<div>
<label for="size" class="block text-sm font-medium text-gray-700">Size</label>
<select name="size" id="size" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select size</option>
<option value="Small">Small</option>
<option value="Medium">Medium</option>
<option value="Large">Large</option>
</select>
</div>
<div>
<label for="care_difficulty" class="block text-sm font-medium text-gray-700">Care Difficulty</label>
<select name="care_difficulty" id="care_difficulty" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select difficulty</option>
<option value="Easy">Easy</option>
<option value="Moderate">Moderate</option>
<option value="Hard">Hard</option>
</select>
</div>
<div>
<label for="growth_rate" class="block text-sm font-medium text-gray-700">Growth Rate</label>
<select name="growth_rate" id="growth_rate" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="">Select growth rate</option>
<option value="Fast">Fast</option>
<option value="Moderate">Moderate</option>
<option value="Slow">Slow</option>
</select>
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Products</label>
<div class="flex flex-wrap gap-4">
{% for product in products %}
<label class="inline-flex items-center">
<input type="checkbox" name="product_ids" value="{{ product.id }}" class="form-checkbox rounded text-[#6b8f71] focus:ring-[#6b8f71]">
<span class="ml-2">{{ product.name }}</span>
</label>
{% endfor %}
</div>
</div>
<div class="md:col-span-2">
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
<textarea name="description" id="description" rows="6" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"></textarea>
</div>
<div class="md:col-span-2">
<label for="care_guide" class="block text-sm font-medium text-gray-700">Care Guide</label>
<div id="quill-care-guide" class="bg-white rounded border border-gray-300" style="min-height: 120px;"></div>
<textarea name="care_guide" id="care_guide" style="display:none;"></textarea>
</div>
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Plant</button>
</form>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full text-left text-sm">
<thead>
<tr class="border-b">
<th class="py-2 px-3">Name</th>
<th class="py-2 px-3">Climate</th>
<th class="py-2 px-3">Environment</th>
<th class="py-2 px-3">Added</th>
<th class="py-2 px-3">Actions</th>
</tr>
</thead>
<tbody>
{% for plant in plants %}
<tr class="border-b hover:bg-[#f5f7f2]">
<td class="py-2 px-3 font-semibold">{{ plant.name }}</td>
<td class="py-2 px-3">{{ climates[plant.climate_id] if plant.climate_id in climates else '' }}</td>
<td class="py-2 px-3">{{ environments[plant.environment_id] if plant.environment_id in environments else '' }}</td>
<td class="py-2 px-3"><span class="local-date" data-date="{{ plant.date_added.isoformat() }}"></span></td>
<td class="py-2 px-3">
<a href="{{ url_for('edit_plant', plant_id=plant.id) }}" class="btn-edit">Edit</a>
<form id="delete-plant-{{ plant.id }}" method="POST" action="{{ url_for('delete_plant', plant_id=plant.id) }}" style="display:inline;">
<button type="button" onclick="showDeleteModal('delete-plant-{{ plant.id }}', 'Are you sure you want to delete {{ plant.name }}?')" class="btn-delete ml-2">Delete</button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="5" class="py-4 text-center text-gray-500">No plants found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script>
document.getElementById('show-add-plant').onclick = function() {
var card = document.getElementById('add-plant-form-card');
card.classList.toggle('hidden');
if (!card.classList.contains('hidden')) {
card.scrollIntoView({behavior: 'smooth'});
}
};
document.getElementById('close-add-plant').onclick = function() {
document.getElementById('add-plant-form-card').classList.add('hidden');
};
var quill = new Quill('#quill-care-guide', { theme: 'snow' });
document.getElementById('add-plant-form').onsubmit = function() {
document.getElementById('care_guide').value = quill.root.innerHTML;
};
</script>
{% endblock %}

View File

@@ -0,0 +1,50 @@
{% extends "admin_base.html" %}
{% block title %}Manage Products{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new product form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Product</h2>
<form method="POST" 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>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Product</button>
</form>
</div>
<!-- List of existing products -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Products</h2>
<div class="space-y-4">
{% for product in products %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div>
<h3 class="font-bold">{{ product.name }}</h3>
{% if product.description %}
<p class="text-gray-600 mt-2">{{ product.description }}</p>
{% endif %}
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_product', product_id=product.id) }}" class="btn-edit">Edit</a>
<form method="POST" action="{{ url_for('delete_product', product_id=product.id) }}" onsubmit="return confirm('Are you sure you want to delete this product?');">
<button type="submit" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No products added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "admin_base.html" %}
{% block title %}Manage Size Categories{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new size category form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Size Category</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 (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Size Category</button>
</form>
</div>
<!-- List of existing size categories -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Size Categories</h2>
<div class="space-y-4">
{% for size in sizes %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div class="flex items-center gap-3">
{% if size.icon %}
<img src="{{ url_for('static', filename='icons/' ~ size.icon) }}" alt="Icon" class="w-8 h-8 inline-block">
{% endif %}
<div>
<h3 class="font-bold">{{ size.name }}</h3>
{% if size.description %}
<p class="text-gray-600 mt-2">{{ size.description }}</p>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_size', size_id=size.id) }}" class="btn-edit">Edit</a>
<form id="delete-size-{{ size.id }}" method="POST" action="{{ url_for('delete_size', size_id=size.id) }}" style="display:inline;">
<button type="button" onclick="showDeleteModal('delete-size-{{ size.id }}', 'Are you sure you want to delete the size category {{ size.name }}?')" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No size categories added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "admin_base.html" %}
{% block title %}Manage Toxicity Levels{% endblock %}
{% block admin_content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Add new toxicity level form -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Add New Toxicity Level</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 (SVG)</label>
<input type="file" name="icon" id="icon" accept=".svg" class="mt-1 block w-full text-sm text-gray-700">
</div>
<button type="submit" class="w-full btn-main text-lg font-semibold">Add Toxicity Level</button>
</form>
</div>
<!-- List of existing toxicity levels -->
<div class="bg-gray-50 p-6 rounded-lg">
<h2 class="text-xl font-bold mb-4">Existing Toxicity Levels</h2>
<div class="space-y-4">
{% for toxicity in toxicities %}
<div class="p-4 rounded-lg shadow flex justify-between items-center">
<div class="flex items-center gap-3">
{% if toxicity.icon %}
<img src="{{ url_for('static', filename='icons/' ~ toxicity.icon) }}" alt="Icon" class="w-8 h-8 inline-block">
{% endif %}
<div>
<h3 class="font-bold">{{ toxicity.name }}</h3>
{% if toxicity.description %}
<p class="text-gray-600 mt-2">{{ toxicity.description }}</p>
{% endif %}
</div>
</div>
<div class="flex space-x-2">
<a href="{{ url_for('edit_toxicity', toxicity_id=toxicity.id) }}" class="btn-edit">Edit</a>
<form id="delete-toxicity-{{ toxicity.id }}" method="POST" action="{{ url_for('delete_toxicity', toxicity_id=toxicity.id) }}" style="display:inline;">
<button type="button" onclick="showDeleteModal('delete-toxicity-{{ toxicity.id }}', 'Are you sure you want to delete the toxicity level {{ toxicity.name }}?')" class="btn-delete">Delete</button>
</form>
</div>
</div>
{% else %}
<p class="text-gray-500">No toxicity levels added yet.</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

93
templates/post.html Normal file
View File

@@ -0,0 +1,93 @@
{% extends "base.html" %}
{% block title %}{{ plant.name }}{% endblock %}
{% block content %}
<article class="max-w-4xl mx-auto bg-white p-8 rounded-2xl shadow-xl mt-8">
<div class="flex flex-col md:flex-row gap-4 md:gap-6">
<div class="md:w-1/2 flex-shrink-0 mb-6 md:mb-0">
{% if plant.picture %}
<img src="{{ url_for('static', filename='uploads/' ~ plant.picture) }}" alt="{{ plant.name }}" class="w-full h-96 object-cover rounded-xl shadow-md border-2 border-[#e6ebe0]">
{% else %}
<img src="{{ url_for('static', filename='images/placeholder-plant.svg') }}" alt="No image available" class="w-full h-96 object-cover rounded-xl shadow-md border-2 border-[#e6ebe0] opacity-50">
{% endif %}
</div>
<div class="md:w-1/2 flex flex-col gap-4">
<h1 class="text-4xl font-bold text-[#4e6b50] mb-2">{{ plant.name }}</h1>
<div class="flex flex-wrap gap-2 mb-2">
{% if plant.climate %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#d0e7d2] text-[#3e5637] text-xs font-semibold">
{% if plant.climate.icon %}<img src="{{ url_for('static', filename='icons/' ~ plant.climate.icon) }}" alt="Climate icon" class="w-4 h-4 mr-1 inline-block align-middle" />{% endif %}
{{ plant.climate.name }}
</span>
{% endif %}
{% if plant.environment %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#d0e7d2] text-[#3e5637] text-xs font-semibold">
{% if plant.environment.icon %}<img src="{{ url_for('static', filename='icons/' ~ plant.environment.icon) }}" alt="Environment icon" class="w-4 h-4 mr-1 inline-block align-middle" />{% endif %}
{{ plant.environment.name }}
</span>
{% endif %}
{% if plant.light %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#e6ebe0] text-[#3e5637] text-xs font-semibold">
{% if plant.light.icon %}<img src="{{ url_for('static', filename='icons/' ~ plant.light.icon) }}" alt="Light icon" class="w-4 h-4 mr-1 inline-block align-middle" />{% endif %}
{{ plant.light.name }}
</span>
{% endif %}
{% if plant.toxicity %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#fff4e6] text-[#3e5637] text-xs font-semibold">
{% if plant.toxicity.icon %}<img src="{{ url_for('static', filename='icons/' ~ plant.toxicity.icon) }}" alt="Toxicity icon" class="w-4 h-4 mr-1 inline-block align-middle" />{% endif %}
{{ plant.toxicity.name }}
</span>
{% endif %}
{% if plant.size %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#e6f7f5] text-[#3e5637] text-xs font-semibold">
{% if plant.size.icon %}<img src="{{ url_for('static', filename='icons/' ~ plant.size.icon) }}" alt="Size icon" class="w-4 h-4 mr-1 inline-block align-middle" />{% endif %}
{{ plant.size.name }}
</span>
{% endif %}
{% if plant.care_difficulty %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#f3e6ff] text-[#3e5637] text-xs font-semibold">
{% if plant.care_difficulty.icon %}<img src="{{ url_for('static', filename='icons/' ~ plant.care_difficulty.icon) }}" alt="Care icon" class="w-4 h-4 mr-1 inline-block align-middle" />{% endif %}
{{ plant.care_difficulty.name }}
</span>
{% endif %}
{% if plant.growth_rate %}
<span class="inline-flex items-center px-2 py-0.5 rounded bg-[#f7fae6] text-[#3e5637] text-xs font-semibold">
{% if plant.growth_rate.icon %}<img src="{{ url_for('static', filename='icons/' ~ plant.growth_rate.icon) }}" alt="Growth icon" class="w-4 h-4 mr-1 inline-block align-middle" />{% endif %}
{{ plant.growth_rate.name }}
</span>
{% endif %}
</div>
<div class="prose max-w-none mb-4 text-[#4e6b50]">{{ plant.description|safe }}</div>
</div>
</div>
{% if products %}
<div class="mb-8 mt-4">
<span class="font-semibold text-[#4e6b50]">Products:</span>
<span class="flex flex-wrap gap-2 mt-1">
{% for product in products %}
<span class="inline-block bg-[#e6ebe0] text-[#3e5637] px-3 py-1 rounded text-xs font-semibold border border-[#d0e7d2]">{{ product.name }}</span>
{% endfor %}
</span>
</div>
{% endif %}
{% if plant.care_guide %}
<div class="mt-12">
<h2 class="text-2xl font-bold text-[#6b8f71] mb-3">Care Guide</h2>
<div class="prose max-w-none bg-[#f8f9fa] border border-[#e6ebe0] rounded-xl p-6 text-[#3e5637] shadow" style="min-height:80px;">
{{ plant.care_guide|safe }}
</div>
</div>
{% endif %}
<div class="text-xs text-[#6b8f71] mt-8">Added on <span class="local-date" data-date="{{ plant.date_added.isoformat() }}"></span></div>
<div class="mt-8 flex flex-wrap gap-4">
<a href="{{ url_for('home', climate=plant.climate_id, environment=plant.environment_id) }}"
class="inline-block bg-[#6b8f71] text-white hover:bg-[#4e6b50] font-semibold px-6 py-2 rounded-lg shadow transition-colors duration-200">
🌱 View Similar Plants
</a>
<a href="{{ url_for('home') }}" class="inline-block bg-[#e6ebe0] text-[#3e5637] hover:bg-[#b7c7a3] hover:text-[#4e6b50] font-semibold px-6 py-2 rounded-lg shadow transition-colors duration-200">
← Back to Home
</a>
</div>
</article>
{% endblock %}