Added Views

This commit is contained in:
2022-07-14 09:56:03 -07:00
parent 17041c9c8b
commit 2d313a9e75
5 changed files with 229 additions and 43 deletions

View File

@@ -89,6 +89,34 @@ def list_delete(session: Session, id: int):
session.commit() session.commit()
def list_deleteItem(session: Session, listId: int, entryId: int):
"""Delete an Entry from a Shopping List."""
entry = list_entry_by_id(session, listId, entryId)
session.delete(entry)
session.commit()
def list_editItem(
session: Session,
listId: int,
entryId: int,
*,
quantity: Optional[str],
notes: Optional[str],
) -> ListEntry:
"""Edit an Entry on a Shopping List."""
entry = list_entry_by_id(session, listId, entryId)
entry.quantity = quantity
entry.notes = notes
entry.set_modified_at()
session.add(entry)
session.commit()
return entry
def list_stores(session: Session, id: int) -> List[str]: def list_stores(session: Session, id: int) -> List[str]:
"""Get a list of all Stores for the List. """Get a list of all Stores for the List.
@@ -132,18 +160,13 @@ def list_update(
return sList return sList
def list_entry_by_id(session: Session, id: int) -> Optional[ListEntry]: def list_entry_by_id(session: Session, listId: int, entryId: int) -> Optional[ListEntry]:
"""Load a specific Shopping List Entry.""" """Load a specific Shopping List Entry."""
return session.query(ListEntry).filter(ListEntry.id == id).one_or_none()
def list_entry_set_crossedOff(session: Session, listId: int, entryId: int, crossedOff: bool) -> ListEntry:
"""Set the Crossed-Off state of a List Entry."""
sList = list_by_id(session, listId) sList = list_by_id(session, listId)
if not sList: if not sList:
raise NotFoundError(f'List {listId} not found') raise NotFoundError(f'List {listId} not found')
entry = list_entry_by_id(session, entryId) entry = session.query(ListEntry).filter(ListEntry.id == entryId).one_or_none()
if not entry: if not entry:
raise NotFoundError(f'List Entry {entryId} not found') raise NotFoundError(f'List Entry {entryId} not found')
@@ -153,6 +176,13 @@ def list_entry_set_crossedOff(session: Session, listId: int, entryId: int, cross
status_code=422, status_code=422,
) )
return entry
def list_entry_set_crossedOff(session: Session, listId: int, entryId: int, crossedOff: bool) -> ListEntry:
"""Set the Crossed-Off state of a List Entry."""
entry = list_entry_by_id(session, listId, entryId)
entry.crossedOff = crossedOff entry.crossedOff = crossedOff
session.add(entry) session.add(entry)
session.commit() session.commit()

View File

@@ -1,7 +1,8 @@
{% extends 'base.html.j2' %} {% extends 'base.html.j2' %}
{% block title %}{{ list.name }} | Sigl{% endblock %} {% block title %}{{ list.name }} | Sigl{% endblock %}
{% block header %} {% block header %}
<div class="flex justify-between items-center py-2"> <div class="flex flex-col py-2" x-data="{notesOpen:false}">
<div class="flex justify-between items-center w-full">
<div class="text-lg font-bold text-gray-800">{{ list.name }}</div> <div class="text-lg font-bold text-gray-800">{{ list.name }}</div>
<div class="flex justify-start items-start pr-1"> <div class="flex justify-start items-start pr-1">
<a href="{{ url_for('lists.addItem', id=list.id) }}" class="px-2 py-1 text-sm text-white bg-green-600 hover:bg-green-700 border rounded flex justify-between items-center"> <a href="{{ url_for('lists.addItem', id=list.id) }}" class="px-2 py-1 text-sm text-white bg-green-600 hover:bg-green-700 border rounded flex justify-between items-center">
@@ -10,22 +11,31 @@
</svg> </svg>
Add Item Add Item
</a> </a>
<a href="#" class="ml-4 py-1 text-sm text-gray-800 hover:text-blue-700"> {% if list.notes %}
<button @click="notesOpen=!notesOpen" class="ml-4 py-1 text-sm text-gray-800 hover:text-blue-700">
<svg x-show="!notesOpen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<svg x-show="notesOpen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
</button>
{% endif %}
<a href="{{ url_for('lists.update', id=list.id) }}" class="ml-4 py-1 text-sm text-gray-800 hover:text-blue-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg> </svg>
</a> </a>
<button id="delete-list-btn" class="ml-3 py-1 text-sm text-gray-800 hover:text-red-700" onclick="deleteList()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" 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>
</div> </div>
</div>
{% if list.notes %}
<div class="p-2 text-sm w-full" x-show="notesOpen">{{ list.notes}}</div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}
{% block main %} {% block main %}
<div class="fixed bottom-0 flex max-w-3xl w-full mx-auto bg-white md:border-l md:border-r border-b md:rounded-b border-gray-300"> <div class="fixed bottom-0 flex max-w-3xl w-full mx-auto bg-white md:border-l md:border-r border-b md:rounded-b border-gray-300">
<div class="w-full flex items-center justify-between px-2 py-2 text-sm"> <div class="w-full flex items-center justify-between px-2 py-2">
<label for="sorting" class="font-bold">Sort By: </label> <label for="sorting" class="font-bold">Sort By: </label>
<select name="sorting" id="sorting" class="flex-grow ml-2 p-1 bg-white border rounded" onchange="changeSorting(event)"> <select name="sorting" id="sorting" class="flex-grow ml-2 p-1 bg-white border rounded" onchange="changeSorting(event)">
<option value="none"{% if sortBy == 'none' %} selected="selected"{% endif %}>None</option> <option value="none"{% if sortBy == 'none' %} selected="selected"{% endif %}>None</option>
@@ -44,14 +54,15 @@
{% macro listEntry(entry, bin=None, last=False) -%} {% macro listEntry(entry, bin=None, last=False) -%}
<li <li
id="item-{{ entry.id }}" id="item-{{ entry.id }}"
x-data='{crossedOff:{{ entry.crossedOff|tojson }}}' x-data='{crossedOff:{{ entry.crossedOff|tojson }},notesOpen:false}'
class="flex items-center justify-between w-full px-2 py-2{% if not last %} border-b border-gray-300{% endif %}" class="flex flex-col w-full px-2 py-2{% if not last %} border-b border-gray-300{% endif %}"
:class="{ :class="{
'bg-gray-400': crossedOff, 'bg-gray-400': crossedOff,
'text-gray-600': crossedOff, 'text-gray-600': crossedOff,
}" }"
> >
<div class="flex items-center justify-start flex-grow" @click.stop.prevent="toggleItem({{ entry.id }}, $data)"> <div class="flex items-center justify-between w-full">
<div class="flex items-center justify-start flex-grow text-lg" @click.stop.prevent="toggleItem({{ entry.id }}, $data)">
<svg xmlns="http://www.w3.org/2000/svg" class="mr-2 h-5 w-5" :class="{'text-gray-200':!crossedOff,'text-gray-600':crossedOff}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <svg xmlns="http://www.w3.org/2000/svg" class="mr-2 h-5 w-5" :class="{'text-gray-200':!crossedOff,'text-gray-600':crossedOff}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
@@ -59,12 +70,29 @@
</div> </div>
<div class="flex items-center justify-end"> <div class="flex items-center justify-end">
<div class="mr-2 text-sm text-gray-600">{% if bin %}Bin {{ bin }}{% endif %}</div> <div class="mr-2 text-sm text-gray-600">{% if bin %}Bin {{ bin }}{% endif %}</div>
<a href="#" class="py-1 text-sm text-gray-800 hover:text-blue-700"> {% if entry.notes or entry.product.notes %}
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <button @click="notesOpen=!notesOpen" class="mr-2 py-1 text-sm text-gray-800 hover:text-blue-700">
<svg x-show="!notesOpen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<svg x-show="notesOpen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
</button>
{% endif %}
<a href="{{ url_for('lists.editItem', listId=list.id, entryId=entry.id) }}" class="py-1 text-sm text-gray-800 hover:text-blue-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg> </svg>
</a> </a>
</div> </div>
</div>
{% if entry.notes %}
<div x-show="notesOpen" class="p-2 text-sm w-full">{{ entry.notes }}</div>
{% endif %}
{% if entry.product.notes %}
<div x-show="notesOpen" class="p-2 text-sm w-full">{{ entry.product.notes }}</div>
{% endif %}
</li> </li>
{%- endmacro %} {%- endmacro %}
{% if sortBy == 'none' %} {% if sortBy == 'none' %}

View File

@@ -0,0 +1,29 @@
{% extends 'base.html.j2' %}
{% block title %}{{ list.name }} | Sigl{% endblock %}
{% block header %}
<div class="flex justify-between items-center py-1">
<div class="text-sm font-bold text-gray-800">Edit Item in {{ list.name }}</div>
<div class="flex justify-start items-start">
<a href="{{ url_for('lists.detail', id=list.id) }}" class="px-2 py-1 text-xs text-white bg-gray-600 hover:bg-gray-700 border rounded flex justify-between items-center">
Cancel
</a>
</div>
</div>
{% endblock %}
{% block main %}
<form method="post">
<div class="py-2 px-4 flex flex-col">
<div class="flex flex-col pb-4">
<label for="quantity" class="py-1 text-xs text-gray-700 font-semibold">Quantity:</label>
<input type="text" name="quantity" id="quantity" class="p-1 text-sm border border-gray-200 rounded" value="{{ entry.quantity }}" />
</div>
<div class="flex flex-col pb-4">
<label for="notes" class="py-1 text-xs text-gray-700 font-semibold">Notes:</label>
<textarea name="notes" id="notes" class="p-1 text-sm border border-gray-200 rounded">{{ entry.notes or '' }}</textarea>
</div>
<div class="flex justify-end">
<button type="submit" class="px-2 py-1 border rounded text-sm text-white bg-blue-600 hover:bg-blue-700">Update Item</button>
</div>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,44 @@
{% extends 'base.html.j2' %}
{% block title %}{{ list.name }} | Sigl{% endblock %}
{% block header %}
<div class="flex justify-between items-center py-2">
<div class="text-lg font-bold text-gray-800">Edit {{ list.name }}</div>
<div class="flex justify-start items-start">
<a href="{{ url_for('lists.detail', id=list.id) }}" class="px-2 py-1 text-xs text-white bg-gray-600 hover:bg-gray-700 border rounded flex justify-between items-center">
Cancel
</a>
<button id="delete-list-btn" class="ml-2 px-2 py-1 text-xs text-white bg-red-600 hover:bg-red-700 border rounded flex justify-between items-center" onclick="deleteList()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" 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>
Delete List
</a>
</div>
</div>
{% endblock %}
{% block main %}
<form method="post">
<div class="py-2 px-4 flex flex-col">
<div class="flex flex-col pb-4">
<label for="name" class="py-1 text-xs text-gray-700 font-semibold">Shopping List Name:</label>
<input type="text" name="name" id="name" class="p-1 text-sm border border-gray-200 rounded" value="{{ list.name }}" />
</div>
<div class="flex flex-col pb-4">
<label for="notes" class="py-1 text-xs text-gray-700 font-semibold">Notes:</label>
<textarea name="notes" id="notes" class="p-1 text-sm border border-gray-200 rounded">{{ list.notes or '' }}</textarea>
</div>
<div class="flex justify-end">
<button type="submit" class="px-2 py-1 border rounded text-sm text-white bg-blue-600 hover:bg-blue-700">Update</button>
</div>
</div>
</form>
<form action="{{ url_for('lists.delete', id=list.id) }}" method="post" id="delete-list-form"></form>
<script language="javascript">
function deleteList() {
const form = document.getElementById('delete-list-form');
if (form && confirm('Are you sure you want to delete list "{{ list.name }}"? This cannot be undone.')) {
form.submit();
}
}
</script>
{% endblock %}

View File

@@ -12,8 +12,9 @@ from flask import (
from sigl.exc import DomainError, Error, NotFoundError from sigl.exc import DomainError, Error, NotFoundError
from sigl.database import db from sigl.database import db
from sigl.domain.service import ( from sigl.domain.service import (
lists_all, list_by_id, list_create, list_delete, list_entry_by_id, lists_all, list_by_id, list_create, list_delete,
list_addItem, list_stores, list_entry_set_crossedOff, list_update, list_addItem, list_editItem, list_stores,
list_entry_set_crossedOff,
products_all, products_all,
) )
@@ -108,6 +109,31 @@ def detail(id):
return redirect(url_for('lists.home')) return redirect(url_for('lists.home'))
@bp.route('/lists/<int:id>/update', methods=('GET', 'POST'))
def update(id):
"""Update a Shopping List."""
try:
sList = list_by_id(db.session, id)
if request.method == 'POST':
list_update(
db.session,
id,
name=request.form.get('name', sList.name),
notes=request.form.get('notes', sList.notes),
)
return redirect(url_for('lists.detail', id=id))
return render_template(
'lists/update.html.j2',
list=sList,
)
except Error as e:
flash(str(e), 'error')
return redirect(url_for('lists.detail', id=id))
@bp.route('/lists/<int:id>/delete', methods=('POST', )) @bp.route('/lists/<int:id>/delete', methods=('POST', ))
def delete(id): def delete(id):
"""Delete a Shopping List.""" """Delete a Shopping List."""
@@ -204,3 +230,32 @@ def addItem(id):
list=sList, list=sList,
products=products, products=products,
) )
@bp.route('/lists/<int:listId>/editItem/<int:entryId>', methods=('GET', 'POST'))
def editItem(listId, entryId):
"""Edit an Item on a Shopping List."""
try:
entry = list_entry_by_id(db.session, listId, entryId)
if request.method == 'POST':
quantity = request.form.get('quantity', None)
notes = request.form.get('notes', None)
list_editItem(
db.session,
listId,
entryId,
quantity=quantity,
notes=notes,
)
return redirect(url_for('lists.detail', id=listId))
except Error as e:
flash(str(e), 'error')
return render_template(
'/lists/editItem.html.j2',
list=entry.shoppingList,
entry=entry,
)