Ahmed semifinal version

This commit is contained in:
Flatlogic Bot 2026-02-09 17:24:55 +00:00
parent 70f713fec0
commit 02ebc63050
9 changed files with 499 additions and 4 deletions

View File

@ -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 %}

View File

@ -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>
<!-- 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 %} {% endblock %}

View 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 %}

View 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 %}

View File

@ -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>

View File

@ -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'),
] ]

View File

@ -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')