Ahmed semifinal version
This commit is contained in:
parent
70f713fec0
commit
02ebc63050
Binary file not shown.
Binary file not shown.
@ -169,6 +169,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'dashboard' %}">Dashboard</a>
|
<a class="nav-link" href="{% url 'dashboard' %}">Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'manage_menu' %}">Menu</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'manage_users' %}">Users</a>
|
<a class="nav-link" href="{% url 'manage_users' %}">Users</a>
|
||||||
</li>
|
</li>
|
||||||
@ -203,7 +206,7 @@
|
|||||||
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}primary{% endif %} border-0 shadow-sm rounded-4 alert-dismissible fade show" role="alert">
|
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}primary{% endif %} border-0 shadow-sm rounded-4 alert-dismissible fade show" role="alert">
|
||||||
<i class="bi {% if message.tags == 'error' %}bi-exclamation-circle{% else %}bi-check-circle{% endif %} me-2"></i>
|
<i class="bi {% if message.tags == 'error' %}bi-exclamation-circle{% else %}bi-check-circle{% endif %} me-2"></i>
|
||||||
{{ message }}
|
{{ message }}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-alert="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -8,7 +8,11 @@
|
|||||||
<p class="text-secondary mb-0">Overview of your restaurant's performance and stock.</p>
|
<p class="text-secondary mb-0">Overview of your restaurant's performance and stock.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<a href="{% url 'manage_users' %}" class="btn btn-outline-vibrant">Manage Users</a>
|
<button type="button" class="btn btn-outline-vibrant" data-bs-toggle="modal" data-bs-target="#addIngredientModal">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i> Add Ingredient
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'manage_menu' %}" class="btn btn-outline-dark">Manage Menu</a>
|
||||||
|
<a href="{% url 'manage_users' %}" class="btn btn-outline-dark">Manage Users</a>
|
||||||
<a href="{% url 'pos' %}" class="btn btn-vibrant px-4">Go to POS</a>
|
<a href="{% url 'pos' %}" class="btn btn-vibrant px-4">Go to POS</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -114,4 +118,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
|
||||||
|
<!-- Add Ingredient Modal -->
|
||||||
|
<div class="modal fade" id="addIngredientModal" tabindex="-1" aria-labelledby="addIngredientModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow rounded-4">
|
||||||
|
<div class="modal-header border-0 pb-0">
|
||||||
|
<h5 class="modal-title fw-bold" id="addIngredientModalLabel">Add New Ingredient</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form action="{% url 'manage_ingredients' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-secondary small fw-bold text-uppercase">Ingredient Name</label>
|
||||||
|
<input type="text" name="name" class="form-control rounded-3" placeholder="e.g. Tomato, Beef Patty" required>
|
||||||
|
</div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label text-secondary small fw-bold text-uppercase">Initial Stock</label>
|
||||||
|
<input type="number" step="0.01" name="stock_quantity" class="form-control rounded-3" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label text-secondary small fw-bold text-uppercase">Unit</label>
|
||||||
|
<select name="unit" class="form-select rounded-3">
|
||||||
|
<option value="grams">grams (g)</option>
|
||||||
|
<option value="ml">milliliters (ml)</option>
|
||||||
|
<option value="pieces">pieces (pcs)</option>
|
||||||
|
<option value="kg">kilograms (kg)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 pt-0 p-4">
|
||||||
|
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-vibrant rounded-pill px-4 shadow-sm">Add Ingredient</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
134
core/templates/core/edit_menu_item.html
Normal file
134
core/templates/core/edit_menu_item.html
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="animate-fade-in">
|
||||||
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 gap-3">
|
||||||
|
<div>
|
||||||
|
<h1 class="section-title mb-1">Edit Menu Item</h1>
|
||||||
|
<p class="text-secondary mb-0">Updating: <span class="fw-bold text-dark">{{ menu_item.name }}</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'manage_menu' %}" class="btn btn-outline-dark">Cancel & Go Back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body p-4 p-md-5">
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label small fw-bold text-secondary">Item Name</label>
|
||||||
|
<input type="text" name="name" class="form-control form-control-lg" value="{{ menu_item.name }}" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label small fw-bold text-secondary">Price (EGP)</label>
|
||||||
|
<input type="number" step="0.01" name="price" class="form-control form-control-lg" value="{{ menu_item.price|stringformat:'.2f' }}" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label small fw-bold text-secondary">Description</label>
|
||||||
|
<textarea name="description" class="form-control" rows="3">{{ menu_item.description }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label small fw-bold text-secondary">Image URL</label>
|
||||||
|
<input type="url" name="image_url" class="form-control" value="{{ menu_item.image_url|default:'' }}">
|
||||||
|
{% if menu_item.image_url %}
|
||||||
|
<div class="mt-2">
|
||||||
|
<img src="{{ menu_item.image_url }}" alt="Preview" class="rounded-3 shadow-sm" style="max-height: 100px;">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="isActive" {% if menu_item.is_active %}checked{% endif %}>
|
||||||
|
<label class="form-check-label small fw-bold text-secondary" for="isActive">Item is Active & Visible in POS</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<hr class="my-4">
|
||||||
|
<h5 class="fw-bold mb-4">Recipe / Ingredients</h5>
|
||||||
|
<div id="ingredient-rows">
|
||||||
|
{% for current in current_ingredients %}
|
||||||
|
<div class="ingredient-row row g-3 mb-3 align-items-end">
|
||||||
|
<div class="col-7">
|
||||||
|
<label class="form-label small text-secondary">Ingredient</label>
|
||||||
|
<select name="ingredients" class="form-select">
|
||||||
|
<option value="">Select Ingredient</option>
|
||||||
|
{% for ing in ingredients %}
|
||||||
|
<option value="{{ ing.id }}" {% if current.ingredient.id == ing.id %}selected{% endif %}>
|
||||||
|
{{ ing.name }} ({{ ing.unit }})
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-label small text-secondary">Qty Required</label>
|
||||||
|
<input type="number" step="0.01" name="quantities" class="form-control" value="{{ current.quantity_required|stringformat:'g' }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button type="button" class="btn btn-outline-danger w-100" onclick="this.parentElement.parentElement.remove()">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="ingredient-row row g-3 mb-3 align-items-end">
|
||||||
|
<div class="col-7">
|
||||||
|
<label class="form-label small text-secondary">Ingredient</label>
|
||||||
|
<select name="ingredients" class="form-select">
|
||||||
|
<option value="">Select Ingredient</option>
|
||||||
|
{% for ing in ingredients %}
|
||||||
|
<option value="{{ ing.id }}">{{ ing.name }} ({{ ing.unit }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<label class="form-label small text-secondary">Qty Required</label>
|
||||||
|
<input type="number" step="0.01" name="quantities" class="form-control" placeholder="0.00">
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button type="button" class="btn btn-outline-danger w-100" onclick="this.parentElement.parentElement.remove()">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-light rounded-pill px-4 mt-3" onclick="addIngredientRow()">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i> Add Another Ingredient
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mt-5">
|
||||||
|
<button type="submit" class="btn btn-vibrant btn-lg w-100 py-3">Update Menu Item</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addIngredientRow() {
|
||||||
|
const container = document.getElementById('ingredient-rows');
|
||||||
|
const firstRow = container.querySelector('.ingredient-row');
|
||||||
|
const newRow = firstRow.cloneNode(true);
|
||||||
|
|
||||||
|
// Clear the values
|
||||||
|
newRow.querySelectorAll('select').forEach(s => s.selectedIndex = 0);
|
||||||
|
newRow.querySelectorAll('input').forEach(i => i.value = '');
|
||||||
|
|
||||||
|
// Ensure the trash button works on the new row
|
||||||
|
newRow.querySelector('.btn-outline-danger').onclick = function() {
|
||||||
|
this.parentElement.parentElement.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
container.appendChild(newRow);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
161
core/templates/core/menu_management.html
Normal file
161
core/templates/core/menu_management.html
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="animate-fade-in">
|
||||||
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 gap-3">
|
||||||
|
<div>
|
||||||
|
<h1 class="section-title mb-1">Menu Management</h1>
|
||||||
|
<p class="text-secondary mb-0">Create and edit your restaurant's menu items.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'dashboard' %}" class="btn btn-outline-dark">Back to Dashboard</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Add Menu Item Form -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card border-0 shadow-sm sticky-top" style="top: 20px;">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h4 class="fw-bold mb-4">Add New Item</h4>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold text-secondary">Item Name</label>
|
||||||
|
<input type="text" name="name" class="form-control" placeholder="e.g. Classic Burger" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold text-secondary">Price (EGP)</label>
|
||||||
|
<input type="number" step="0.01" name="price" class="form-control" placeholder="0.00" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold text-secondary">Description</label>
|
||||||
|
<textarea name="description" class="form-control" rows="2" placeholder="Brief description..."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small fw-bold text-secondary">Image URL (Optional)</label>
|
||||||
|
<input type="url" name="image_url" class="form-control" placeholder="https://...">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="isActive" checked>
|
||||||
|
<label class="form-check-label small fw-bold text-secondary" for="isActive">Active & Visible</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
<h6 class="fw-bold mb-3">Recipe / Ingredients</h6>
|
||||||
|
<div id="ingredient-rows">
|
||||||
|
<div class="ingredient-row row g-2 mb-2">
|
||||||
|
<div class="col-7">
|
||||||
|
<select name="ingredients" class="form-select form-select-sm">
|
||||||
|
<option value="">Select Ingredient</option>
|
||||||
|
{% for ing in ingredients %}
|
||||||
|
<option value="{{ ing.id }}">{{ ing.name }} ({{ ing.unit }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<input type="number" step="0.01" name="quantities" class="form-control form-control-sm" placeholder="Qty">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-light w-100 mt-2" onclick="addIngredientRow()">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i> Add Another Ingredient
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-vibrant w-100 mt-4 py-3">Create Menu Item</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menu Item List -->
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h4 class="fw-bold mb-4">Current Menu</h4>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr class="text-secondary small">
|
||||||
|
<th class="py-3 border-0 rounded-start">Item</th>
|
||||||
|
<th class="py-3 border-0 text-center">Price</th>
|
||||||
|
<th class="py-3 border-0 text-center">Status</th>
|
||||||
|
<th class="py-3 border-0 text-end rounded-end">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in menu_items %}
|
||||||
|
<tr>
|
||||||
|
<td class="py-3">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
{% if item.image_url %}
|
||||||
|
<img src="{{ item.image_url }}" alt="{{ item.name }}" class="rounded-3 me-3" style="width: 45px; height: 45px; object-fit: cover;">
|
||||||
|
{% else %}
|
||||||
|
<div class="bg-light rounded-3 me-3 d-flex align-items-center justify-content-center" style="width: 45px; height: 45px;">
|
||||||
|
<i class="bi bi-egg-fried text-secondary"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold text-dark">{{ item.name }}</div>
|
||||||
|
<div class="small text-secondary">{{ item.ingredients.count }} ingredients</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-center fw-bold">{{ item.price|floatformat:2 }} EGP</td>
|
||||||
|
<td class="text-center">
|
||||||
|
{% if item.is_active %}
|
||||||
|
<span class="badge bg-success bg-opacity-10 text-success rounded-pill px-3">Active</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary bg-opacity-10 text-secondary rounded-pill px-3">Inactive</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-sm btn-light rounded-pill px-3" data-bs-toggle="dropdown">
|
||||||
|
<i class="bi bi-three-dots-vertical"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end border-0 shadow-sm">
|
||||||
|
<li><a class="dropdown-item py-2" href="{% url 'edit_menu_item' item.pk %}"><i class="bi bi-pencil me-2"></i> Edit Item</a></li>
|
||||||
|
<li><a class="dropdown-item py-2" href="{% url 'toggle_menu_item_status' item.pk %}">
|
||||||
|
{% if item.is_active %}
|
||||||
|
<i class="bi bi-eye-slash me-2"></i> Deactivate
|
||||||
|
{% else %}
|
||||||
|
<i class="bi bi-eye me-2"></i> Activate
|
||||||
|
{% endif %}
|
||||||
|
</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-5">
|
||||||
|
<p class="text-secondary mb-0">No menu items found. Add your first one!</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addIngredientRow() {
|
||||||
|
const container = document.getElementById('ingredient-rows');
|
||||||
|
const firstRow = container.querySelector('.ingredient-row');
|
||||||
|
const newRow = firstRow.cloneNode(true);
|
||||||
|
|
||||||
|
// Clear the selected option and quantity
|
||||||
|
newRow.querySelector('select').selectedIndex = 0;
|
||||||
|
newRow.querySelector('input').value = '';
|
||||||
|
|
||||||
|
container.appendChild(newRow);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -97,6 +97,12 @@
|
|||||||
<small class="opacity-50">at {{ u.date_joined|date:"H:i" }}</small>
|
<small class="opacity-50">at {{ u.date_joined|date:"H:i" }}</small>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
|
{% if u.id != request.user.id %}
|
||||||
|
<a href="{% url 'delete_user' u.id %}" class="btn btn-sm btn-outline-danger rounded-circle mx-1"
|
||||||
|
onclick="return confirm('Are you sure you want to delete user {{ u.username }}?')" title="Delete User">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
<button class="btn btn-sm btn-light rounded-circle" title="Edit User"><i class="bi bi-pencil"></i></button>
|
<button class="btn btn-sm btn-light rounded-circle" title="Edit User"><i class="bi bi-pencil"></i></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -119,4 +125,4 @@
|
|||||||
<style>
|
<style>
|
||||||
.rounded-4 { border-radius: 1rem !important; }
|
.rounded-4 { border-radius: 1rem !important; }
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -10,4 +10,9 @@ urlpatterns = [
|
|||||||
path('receipt/<str:order_number>/', views.receipt_view, name='receipt'),
|
path('receipt/<str:order_number>/', views.receipt_view, name='receipt'),
|
||||||
path('dashboard/', views.dashboard_view, name='dashboard'),
|
path('dashboard/', views.dashboard_view, name='dashboard'),
|
||||||
path('manage-users/', views.manage_users, name='manage_users'),
|
path('manage-users/', views.manage_users, name='manage_users'),
|
||||||
|
path('delete-user/<int:user_id>/', views.delete_user, name='delete_user'),
|
||||||
|
path('manage-ingredients/', views.manage_ingredients, name='manage_ingredients'),
|
||||||
|
path('manage-menu/', views.manage_menu, name='manage_menu'),
|
||||||
|
path('edit-menu-item/<int:pk>/', views.edit_menu_item, name='edit_menu_item'),
|
||||||
|
path('toggle-menu-item/<int:pk>/', views.toggle_menu_item_status, name='toggle_menu_item_status'),
|
||||||
]
|
]
|
||||||
|
|||||||
142
core/views.py
142
core/views.py
@ -144,3 +144,145 @@ def manage_users(request):
|
|||||||
messages.error(request, "Please fill all fields.")
|
messages.error(request, "Please fill all fields.")
|
||||||
|
|
||||||
return render(request, 'core/user_management.html', {'users': users})
|
return render(request, 'core/user_management.html', {'users': users})
|
||||||
|
|
||||||
|
@manager_required
|
||||||
|
def delete_user(request, user_id):
|
||||||
|
"""Manager only: Delete a user account."""
|
||||||
|
if request.user.id == user_id:
|
||||||
|
messages.error(request, "You cannot delete your own account.")
|
||||||
|
return redirect('manage_users')
|
||||||
|
|
||||||
|
user = get_object_or_404(User, id=user_id)
|
||||||
|
username = user.username
|
||||||
|
user.delete()
|
||||||
|
messages.success(request, f"User {username} has been deleted.")
|
||||||
|
return redirect('manage_users')
|
||||||
|
|
||||||
|
@manager_required
|
||||||
|
def manage_ingredients(request):
|
||||||
|
"""Manager only: View and add ingredients."""
|
||||||
|
if request.method == 'POST':
|
||||||
|
name = request.POST.get('name')
|
||||||
|
stock_quantity = request.POST.get('stock_quantity', 0)
|
||||||
|
unit = request.POST.get('unit', 'grams')
|
||||||
|
|
||||||
|
if name:
|
||||||
|
if Ingredient.objects.filter(name__iexact=name).exists():
|
||||||
|
messages.error(request, f"Ingredient '{name}' already exists.")
|
||||||
|
else:
|
||||||
|
Ingredient.objects.create(
|
||||||
|
name=name,
|
||||||
|
stock_quantity=stock_quantity,
|
||||||
|
unit=unit
|
||||||
|
)
|
||||||
|
messages.success(request, f"Ingredient '{name}' added successfully.")
|
||||||
|
else:
|
||||||
|
messages.error(request, "Ingredient name is required.")
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
return redirect('dashboard')
|
||||||
|
|
||||||
|
@manager_required
|
||||||
|
def manage_menu(request):
|
||||||
|
"""Manager only: View and create menu items."""
|
||||||
|
menu_items = MenuItem.objects.all()
|
||||||
|
ingredients = Ingredient.objects.all()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
name = request.POST.get('name')
|
||||||
|
price = request.POST.get('price')
|
||||||
|
description = request.POST.get('description', '')
|
||||||
|
image_url = request.POST.get('image_url', '')
|
||||||
|
is_active = request.POST.get('is_active') == 'on'
|
||||||
|
|
||||||
|
# Handle ingredient IDs and quantities from the form
|
||||||
|
ingredient_ids = request.POST.getlist('ingredients')
|
||||||
|
quantities = request.POST.getlist('quantities')
|
||||||
|
|
||||||
|
if name and price:
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
menu_item = MenuItem.objects.create(
|
||||||
|
name=name,
|
||||||
|
price=price,
|
||||||
|
description=description,
|
||||||
|
image_url=image_url,
|
||||||
|
is_active=is_active
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add ingredients if provided
|
||||||
|
for i_id, qty in zip(ingredient_ids, quantities):
|
||||||
|
if i_id and qty and float(qty) > 0:
|
||||||
|
ingredient = get_object_or_404(Ingredient, id=i_id)
|
||||||
|
MenuItemIngredient.objects.create(
|
||||||
|
menu_item=menu_item,
|
||||||
|
ingredient=ingredient,
|
||||||
|
quantity_required=qty
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.success(request, f"Menu item '{name}' created successfully.")
|
||||||
|
return redirect('manage_menu')
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, f"Error creating menu item: {str(e)}")
|
||||||
|
else:
|
||||||
|
messages.error(request, "Name and price are required.")
|
||||||
|
|
||||||
|
return render(request, 'core/menu_management.html', {
|
||||||
|
'menu_items': menu_items,
|
||||||
|
'ingredients': ingredients
|
||||||
|
})
|
||||||
|
|
||||||
|
@manager_required
|
||||||
|
def edit_menu_item(request, pk):
|
||||||
|
"""Manager only: Edit an existing menu item."""
|
||||||
|
menu_item = get_object_or_404(MenuItem, pk=pk)
|
||||||
|
ingredients = Ingredient.objects.all()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
menu_item.name = request.POST.get('name')
|
||||||
|
menu_item.price = request.POST.get('price')
|
||||||
|
menu_item.description = request.POST.get('description', '')
|
||||||
|
menu_item.image_url = request.POST.get('image_url', '')
|
||||||
|
menu_item.is_active = request.POST.get('is_active') == 'on'
|
||||||
|
|
||||||
|
ingredient_ids = request.POST.getlist('ingredients')
|
||||||
|
quantities = request.POST.getlist('quantities')
|
||||||
|
|
||||||
|
if menu_item.name and menu_item.price:
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
menu_item.save()
|
||||||
|
|
||||||
|
# Update ingredients: Clear existing and re-add
|
||||||
|
menu_item.ingredients.all().delete()
|
||||||
|
for i_id, qty in zip(ingredient_ids, quantities):
|
||||||
|
if i_id and qty and float(qty) > 0:
|
||||||
|
ingredient = get_object_or_404(Ingredient, id=i_id)
|
||||||
|
MenuItemIngredient.objects.create(
|
||||||
|
menu_item=menu_item,
|
||||||
|
ingredient=ingredient,
|
||||||
|
quantity_required=qty
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.success(request, f"Menu item '{menu_item.name}' updated successfully.")
|
||||||
|
return redirect('manage_menu')
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, f"Error updating menu item: {str(e)}")
|
||||||
|
else:
|
||||||
|
messages.error(request, "Name and price are required.")
|
||||||
|
|
||||||
|
return render(request, 'core/edit_menu_item.html', {
|
||||||
|
'menu_item': menu_item,
|
||||||
|
'ingredients': ingredients,
|
||||||
|
'current_ingredients': menu_item.ingredients.all()
|
||||||
|
})
|
||||||
|
|
||||||
|
@manager_required
|
||||||
|
def toggle_menu_item_status(request, pk):
|
||||||
|
"""Manager only: Toggle menu item active/inactive status."""
|
||||||
|
menu_item = get_object_or_404(MenuItem, pk=pk)
|
||||||
|
menu_item.is_active = not menu_item.is_active
|
||||||
|
menu_item.save()
|
||||||
|
status = "activated" if menu_item.is_active else "deactivated"
|
||||||
|
messages.success(request, f"Menu item '{menu_item.name}' {status}.")
|
||||||
|
return redirect('manage_menu')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user