Initial commit with project setup
This commit is contained in:
104
templates/admin_base.html
Normal file
104
templates/admin_base.html
Normal 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
161
templates/base.html
Normal 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
106
templates/create_plant.html
Normal 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 %}
|
||||
32
templates/edit_care_difficulty.html
Normal file
32
templates/edit_care_difficulty.html
Normal 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 %}
|
||||
33
templates/edit_climate.html
Normal file
33
templates/edit_climate.html
Normal 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 %}
|
||||
33
templates/edit_environment.html
Normal file
33
templates/edit_environment.html
Normal 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 %}
|
||||
32
templates/edit_growth_rate.html
Normal file
32
templates/edit_growth_rate.html
Normal 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
32
templates/edit_light.html
Normal 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
128
templates/edit_plant.html
Normal 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 %}
|
||||
26
templates/edit_product.html
Normal file
26
templates/edit_product.html
Normal 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
32
templates/edit_size.html
Normal 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 %}
|
||||
32
templates/edit_toxicity.html
Normal file
32
templates/edit_toxicity.html
Normal 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
173
templates/home.html
Normal 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">×</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
20
templates/login.html
Normal 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 %}
|
||||
139
templates/manage_attributes.html
Normal file
139
templates/manage_attributes.html
Normal 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 %}
|
||||
59
templates/manage_care_difficulties.html
Normal file
59
templates/manage_care_difficulties.html
Normal 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 %}
|
||||
59
templates/manage_climates.html
Normal file
59
templates/manage_climates.html
Normal 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 %}
|
||||
59
templates/manage_environments.html
Normal file
59
templates/manage_environments.html
Normal 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 %}
|
||||
59
templates/manage_growth_rates.html
Normal file
59
templates/manage_growth_rates.html
Normal 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 %}
|
||||
59
templates/manage_lights.html
Normal file
59
templates/manage_lights.html
Normal 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 %}
|
||||
170
templates/manage_plants.html
Normal file
170
templates/manage_plants.html
Normal 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">×</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 %}
|
||||
50
templates/manage_products.html
Normal file
50
templates/manage_products.html
Normal 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 %}
|
||||
59
templates/manage_sizes.html
Normal file
59
templates/manage_sizes.html
Normal 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 %}
|
||||
59
templates/manage_toxicities.html
Normal file
59
templates/manage_toxicities.html
Normal 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
93
templates/post.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user