Set purchase date and price
This commit is contained in:
parent
195f18f141
commit
f0cec23da9
@ -168,6 +168,15 @@
|
||||
# Default: 3333
|
||||
# BK_PORT=3333
|
||||
|
||||
# Optional: Format of the timestamp for purchase dates
|
||||
# Check https://docs.python.org/3/library/time.html#time.strftime for format details
|
||||
# Default: %d/%m/%Y
|
||||
# BK_PURCHASE_DATE_FORMAT=%m/%d/%Y
|
||||
|
||||
# Optional: Currency to display for purchase prices.
|
||||
# Default: €
|
||||
# BK_PURCHASE_CURRENCY=£
|
||||
|
||||
# Optional: Change the default order of purchase locations. By default ordered by insertion order.
|
||||
# Useful column names for this option are:
|
||||
# - "bricktracker_metadata_purchase_locations"."name" ASC: storage name
|
||||
|
@ -16,6 +16,8 @@
|
||||
- Added: `BK_HIDE_ALL_STORAGES`, hide the "Storages" menu entry
|
||||
- Added: `BK_STORAGE_DEFAULT_ORDER`, ordering of storages
|
||||
- Added: `BK_PURCHASE_LOCATION_DEFAULT_ORDER`, ordering of purchase locations
|
||||
- Added: `BK_PURCHASE_CURRENCY`, currency to display for purchase prices
|
||||
- Added: `BK_PURCHASE_DATE_FORMAT`, date format for purchase dates
|
||||
|
||||
### Code
|
||||
|
||||
@ -40,7 +42,7 @@
|
||||
- Ownership
|
||||
- Tags
|
||||
- Storage
|
||||
- Purchase location
|
||||
- Purchase location, date, price
|
||||
|
||||
- Storage
|
||||
- Storage content and list
|
||||
@ -87,7 +89,7 @@
|
||||
- Tags
|
||||
- Refresh
|
||||
- Storage
|
||||
- Purchase location
|
||||
- Purchase location, date, price
|
||||
|
||||
- Sets grid
|
||||
- Collapsible controls depending on screen size
|
||||
|
@ -41,6 +41,8 @@ CONFIG: Final[list[dict[str, Any]]] = [
|
||||
{'n': 'PARTS_DEFAULT_ORDER', 'd': '"rebrickable_parts"."name" ASC, "rebrickable_parts"."color_name" ASC, "bricktracker_parts"."spare" ASC'}, # noqa: E501
|
||||
{'n': 'PARTS_FOLDER', 'd': 'parts', 's': True},
|
||||
{'n': 'PORT', 'd': 3333, 'c': int},
|
||||
{'n': 'PURCHASE_DATE_FORMAT', 'd': '%d/%m/%Y'},
|
||||
{'n': 'PURCHASE_CURRENCY', 'd': '€'},
|
||||
{'n': 'PURCHASE_LOCATION_DEFAULT_ORDER', 'd': '"bricktracker_metadata_purchase_locations"."name" ASC'}, # noqa: E501
|
||||
{'n': 'RANDOM', 'e': 'RANDOM', 'c': bool},
|
||||
{'n': 'REBRICKABLE_API_KEY', 'e': 'REBRICKABLE_API_KEY', 'd': ''},
|
||||
|
@ -1,3 +1,4 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import traceback
|
||||
from typing import Any, Self, TYPE_CHECKING
|
||||
@ -5,7 +6,7 @@ from uuid import uuid4
|
||||
|
||||
from flask import current_app, url_for
|
||||
|
||||
from .exceptions import NotFoundException
|
||||
from .exceptions import NotFoundException, DatabaseException, ErrorException
|
||||
from .minifigure_list import BrickMinifigureList
|
||||
from .part_list import BrickPartList
|
||||
from .rebrickable_set import RebrickableSet
|
||||
@ -27,6 +28,8 @@ class BrickSet(RebrickableSet):
|
||||
select_query: str = 'set/select/full'
|
||||
light_query: str = 'set/select/light'
|
||||
insert_query: str = 'set/insert'
|
||||
update_purchase_date_query: str = 'set/update/purchase_date'
|
||||
update_purchase_price_query: str = 'set/update/purchase_price'
|
||||
|
||||
# Delete a set
|
||||
def delete(self, /) -> None:
|
||||
@ -152,6 +155,30 @@ class BrickSet(RebrickableSet):
|
||||
|
||||
return True
|
||||
|
||||
# Purchase date
|
||||
def purchase_date(self, /, *, standard: bool = False) -> str:
|
||||
if self.fields.purchase_date is not None:
|
||||
time = datetime.fromtimestamp(self.fields.purchase_date)
|
||||
|
||||
if standard:
|
||||
return time.strftime('%Y/%m/%d')
|
||||
else:
|
||||
return time.strftime(
|
||||
current_app.config['PURCHASE_DATE_FORMAT']
|
||||
)
|
||||
else:
|
||||
return ''
|
||||
|
||||
# Purchase price with currency
|
||||
def purchase_price(self, /) -> str:
|
||||
if self.fields.purchase_price is not None:
|
||||
return '{price}{currency}'.format(
|
||||
price=self.fields.purchase_price,
|
||||
currency=current_app.config['PURCHASE_CURRENCY']
|
||||
)
|
||||
else:
|
||||
return ''
|
||||
|
||||
# Minifigures
|
||||
def minifigures(self, /) -> BrickMinifigureList:
|
||||
return BrickMinifigureList().from_set(self)
|
||||
@ -194,6 +221,80 @@ class BrickSet(RebrickableSet):
|
||||
|
||||
return self
|
||||
|
||||
# Update the purchase date
|
||||
def update_purchase_date(self, json: Any | None, /) -> Any:
|
||||
value = json.get('value', None) # type: ignore
|
||||
|
||||
try:
|
||||
if value == '':
|
||||
value = None
|
||||
|
||||
if value is not None:
|
||||
value = datetime.strptime(value, '%Y/%m/%d').timestamp()
|
||||
except Exception:
|
||||
raise ErrorException('{value} is not a date'.format(
|
||||
value=value,
|
||||
))
|
||||
|
||||
self.fields.purchase_date = value
|
||||
|
||||
rows, _ = BrickSQL().execute_and_commit(
|
||||
self.update_purchase_date_query,
|
||||
parameters=self.sql_parameters()
|
||||
)
|
||||
|
||||
if rows != 1:
|
||||
raise DatabaseException('Could not update the purchase date for set {set} ({id})'.format( # noqa: E501
|
||||
set=self.fields.set,
|
||||
id=self.fields.id,
|
||||
))
|
||||
|
||||
# Info
|
||||
logger.info('Purchase date changed to "{value}" for set {set} ({id})'.format( # noqa: E501
|
||||
value=value,
|
||||
set=self.fields.set,
|
||||
id=self.fields.id,
|
||||
))
|
||||
|
||||
return value
|
||||
|
||||
# Update the purchase price
|
||||
def update_purchase_price(self, json: Any | None, /) -> Any:
|
||||
value = json.get('value', None) # type: ignore
|
||||
|
||||
try:
|
||||
if value == '':
|
||||
value = None
|
||||
|
||||
if value is not None:
|
||||
value = float(value)
|
||||
except Exception:
|
||||
raise ErrorException('{value} is not a number or empty'.format(
|
||||
value=value,
|
||||
))
|
||||
|
||||
self.fields.purchase_price = value
|
||||
|
||||
rows, _ = BrickSQL().execute_and_commit(
|
||||
self.update_purchase_price_query,
|
||||
parameters=self.sql_parameters()
|
||||
)
|
||||
|
||||
if rows != 1:
|
||||
raise DatabaseException('Could not update the purchase price for set {set} ({id})'.format( # noqa: E501
|
||||
set=self.fields.set,
|
||||
id=self.fields.id,
|
||||
))
|
||||
|
||||
# Info
|
||||
logger.info('Purchase price changed to "{value}" for set {set} ({id})'.format( # noqa: E501
|
||||
value=value,
|
||||
set=self.fields.set,
|
||||
id=self.fields.id,
|
||||
))
|
||||
|
||||
return value
|
||||
|
||||
# Self url
|
||||
def url(self, /) -> str:
|
||||
return url_for('set.details', id=self.fields.id)
|
||||
@ -230,3 +331,11 @@ class BrickSet(RebrickableSet):
|
||||
return url_for('storage.details', id=self.fields.storage)
|
||||
else:
|
||||
return ''
|
||||
|
||||
# Update purchase date url
|
||||
def url_for_purchase_date(self, /) -> str:
|
||||
return url_for('set.update_purchase_date', id=self.fields.id)
|
||||
|
||||
# Update purchase price url
|
||||
def url_for_purchase_price(self, /) -> str:
|
||||
return url_for('set.update_purchase_price', id=self.fields.id)
|
||||
|
@ -27,7 +27,7 @@ CREATE TABLE "bricktracker_sets" (
|
||||
"set" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"storage" TEXT, -- Storage bin location
|
||||
"purchase_date" INTEGER, -- Purchase data
|
||||
"purchase_date" REAL, -- Purchase data
|
||||
"purchase_location" TEXT, -- Purchase location
|
||||
"purchase_price" REAL, -- Purchase price
|
||||
PRIMARY KEY("id"),
|
||||
|
@ -1,7 +1,9 @@
|
||||
SELECT
|
||||
{% block id %}{% endblock %}
|
||||
"bricktracker_sets"."storage",
|
||||
"bricktracker_sets"."purchase_date",
|
||||
"bricktracker_sets"."purchase_location",
|
||||
"bricktracker_sets"."purchase_price",
|
||||
"rebrickable_sets"."set",
|
||||
"rebrickable_sets"."number",
|
||||
"rebrickable_sets"."version",
|
||||
|
3
bricktracker/sql/set/update/purchase_date.sql
Normal file
3
bricktracker/sql/set/update/purchase_date.sql
Normal file
@ -0,0 +1,3 @@
|
||||
UPDATE "bricktracker_sets"
|
||||
SET "purchase_date" = :purchase_date
|
||||
WHERE "bricktracker_sets"."id" IS NOT DISTINCT FROM :id
|
3
bricktracker/sql/set/update/purchase_price.sql
Normal file
3
bricktracker/sql/set/update/purchase_price.sql
Normal file
@ -0,0 +1,3 @@
|
||||
UPDATE "bricktracker_sets"
|
||||
SET "purchase_price" = :purchase_price
|
||||
WHERE "bricktracker_sets"."id" IS NOT DISTINCT FROM :id
|
@ -41,6 +41,18 @@ def list() -> str:
|
||||
)
|
||||
|
||||
|
||||
# Change the value of purchase date
|
||||
@set_page.route('/<id>/purchase_date', methods=['POST'])
|
||||
@login_required
|
||||
@exception_handler(__file__, json=True)
|
||||
def update_purchase_date(*, id: str) -> Response:
|
||||
brickset = BrickSet().select_light(id)
|
||||
|
||||
value = brickset.update_purchase_date(request.json)
|
||||
|
||||
return jsonify({'value': value})
|
||||
|
||||
|
||||
# Change the value of purchase location
|
||||
@set_page.route('/<id>/purchase_location', methods=['POST'])
|
||||
@login_required
|
||||
@ -60,6 +72,18 @@ def update_purchase_location(*, id: str) -> Response:
|
||||
return jsonify({'value': value})
|
||||
|
||||
|
||||
# Change the value of purchase price
|
||||
@set_page.route('/<id>/purchase_price', methods=['POST'])
|
||||
@login_required
|
||||
@exception_handler(__file__, json=True)
|
||||
def update_purchase_price(*, id: str) -> Response:
|
||||
brickset = BrickSet().select_light(id)
|
||||
|
||||
value = brickset.update_purchase_price(request.json)
|
||||
|
||||
return jsonify({'value': value})
|
||||
|
||||
|
||||
# Change the state of a owner
|
||||
@set_page.route('/<id>/owner/<metadata_id>', methods=['POST'])
|
||||
@login_required
|
||||
|
@ -21,6 +21,7 @@ It also uses the following libraries and frameworks:
|
||||
- `tinysort` (https://github.com/Sjeiti/TinySort)
|
||||
- `sortable` (https://github.com/tofsjonas/sortable)
|
||||
- `simple-datatables` (https://github.com/fiduswriter/simple-datatables)
|
||||
- `vanillajs-datepicker` (https://github.com/mymth/vanillajs-datepicker)
|
||||
|
||||
The BrickTracker brick logo is part of the Small n' Flat Icons set designed by [Arnaud Chesne](https://iconduck.com/designers/arnaud-chesne).
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Generic state changer with visual feedback
|
||||
// Tooltips require boostrap.Tooltip
|
||||
// Tooltips requires boostrap.Tooltip
|
||||
// Date requires vanillajs-datepicker
|
||||
class BrickChanger {
|
||||
constructor(prefix, id, url, parent = undefined) {
|
||||
this.prefix = prefix
|
||||
@ -51,6 +52,20 @@ class BrickChanger {
|
||||
changer.change();
|
||||
})(this));
|
||||
}
|
||||
|
||||
// Date picker
|
||||
this.picker = undefined;
|
||||
if (this.html_element.dataset.changerDate == "true") {
|
||||
this.picker = new Datepicker(this.html_element, {
|
||||
buttonClass: 'btn',
|
||||
format: 'yyyy/mm/dd',
|
||||
});
|
||||
|
||||
// Picker fires a custom "changeDate" event
|
||||
this.html_element.addEventListener("changeDate", ((changer) => (e) => {
|
||||
changer.change();
|
||||
})(this));
|
||||
}
|
||||
}
|
||||
|
||||
// Clean the status
|
||||
|
@ -9,6 +9,7 @@
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.12.0/baguetteBox.css" integrity="sha512-VZ783G3QIpxXpg7tWpzHn+XhjsOCIxFYoSWmyipKCB41OYaB9i4brxAWuY1c8gGCSqKo7uvckzPJhYcdBZQ9gg==" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/simple-datatables@9.2.1/dist/style.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.3.4/dist/css/datepicker-bs5.min.css">
|
||||
<link href="{{ url_for('static', filename='styles.css') }}" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="{{ url_for('static', filename='brick.png') }}">
|
||||
</head>
|
||||
@ -78,6 +79,7 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.12.0/baguetteBox.min.js" integrity="sha512-HzIuiABxntLbBS8ClRa7drXZI3cqvkAZ5DD0JCAkmRwUtykSGqzA9uItHivDhRUYnW3MMyY5xqk7qVUHOEMbMA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.8.1/socket.io.min.js" integrity="sha512-8ExARjWWkIllMlNzVg7JKq9RKWPlJABQUNq6YvAjE/HobctjH/NA+bSiDMDvouBVjp4Wwnf1VP1OEv7Zgjtuxw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@9.2.1/dist/umd/simple-datatables.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.3.4/dist/js/datepicker-full.min.js"></script>
|
||||
<!-- BrickTracker scripts -->
|
||||
<script src="{{ url_for('static', filename='scripts/changer.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='scripts/grid/filter.js') }}"></script>
|
||||
|
@ -65,6 +65,15 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro purchase_date(date, solo=false, last=false) %}
|
||||
{% if last %}
|
||||
{% set tooltip=date %}
|
||||
{% else %}
|
||||
{% set text=date %}
|
||||
{% endif %}
|
||||
{{ badge(check=date, solo=solo, last=last, color='light border', icon='calendar-line', text=text, tooltip=tooltip, collapsible='Date:') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro purchase_location(item, purchase_locations, solo=false, last=false) %}
|
||||
{% if purchase_locations and item.fields.purchase_location in purchase_locations.mapping %}
|
||||
{% set purchase_location = purchase_locations.mapping[item.fields.purchase_location] %}
|
||||
@ -73,10 +82,19 @@
|
||||
{% else %}
|
||||
{% set text=purchase_location.fields.name %}
|
||||
{% endif %}
|
||||
{{ badge(check=purchase_location, solo=solo, last=last, color='light border', icon='building-line', text=text, tooltip=tooltip) }}
|
||||
{{ badge(check=purchase_location, solo=solo, last=last, color='light border', icon='building-line', text=text, tooltip=tooltip, collapsible='Location:') }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro purchase_price(price, solo=false, last=false) %}
|
||||
{% if last %}
|
||||
{% set tooltip=price %}
|
||||
{% else %}
|
||||
{% set text=price %}
|
||||
{% endif %}
|
||||
{{ badge(check=price, solo=solo, last=last, color='light border', icon='wallet-3-line', text=text, tooltip=tooltip, collapsible='Price:') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro set(set, solo=false, last=false, url=None, id=None) %}
|
||||
{% if id %}
|
||||
{% set url=url_for('set.details', id=id) %}
|
||||
|
@ -17,19 +17,22 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro input(name, id, prefix, url, value, all=none, read_only=none) %}
|
||||
{% macro input(name, id, prefix, url, value, all=none, read_only=none, icon=none, suffix=none, date=false) %}
|
||||
{% if all or read_only %}
|
||||
{{ value }}
|
||||
{% else %}
|
||||
<label class="visually-hidden" for="{{ prefix }}-{{ id }}">{{ name }}</label>
|
||||
<div class="input-group">
|
||||
{% if icon %}<span class="input-group-text"><i class="ri-{{ icon }} me-1"></i><span class="ms-1 d-none d-md-inline"> {{ name }}</span></span>{% endif %}
|
||||
<input class="form-control form-control-sm flex-shrink-1" type="text" id="{{ prefix }}-{{ id }}" value="{% if value %}{{ value }}{% endif %}"
|
||||
{% if g.login.is_authenticated() %}
|
||||
data-changer-id="{{ id }}" data-changer-prefix="{{ prefix }}" data-changer-url="{{ url }}"
|
||||
{% if date %}data-changer-date="true"{% endif %}
|
||||
{% else %}
|
||||
disabled
|
||||
{% endif %}
|
||||
autocomplete="off">
|
||||
{% if suffix %}<span class="input-group-text d-none d-md-inline">{{ suffix }}</span>{% endif %}
|
||||
{% if g.login.is_authenticated() %}
|
||||
<span id="status-{{ prefix }}-{{ id }}" class="input-group-text ri-save-line"></span>
|
||||
<button id="clear-{{ prefix }}-{{ id }}" type="button" class="btn btn-sm btn-light btn-outline-danger border"><i class="ri-eraser-line"></i></button>
|
||||
@ -45,7 +48,7 @@
|
||||
{% set prefix=metadata_list.as_prefix() %}
|
||||
<label class="visually-hidden" for="{{ prefix }}-{{ item.fields.id }}">{{ name }}</label>
|
||||
<div class="input-group">
|
||||
{% if icon %}<span class="input-group-text"><i class="ri-{{ icon }}"></i></span>{% endif %}
|
||||
{% if icon %}<span class="input-group-text"><i class="ri-{{ icon }} me-1"></i><span class="ms-1 d-none d-md-inline"> {{ name }}</span></span>{% endif %}
|
||||
<select id="{{ prefix }}-{{ item.fields.id }}" class="form-select"
|
||||
{% if not delete %}
|
||||
data-changer-id="{{ item.fields.id }}" data-changer-prefix="{{ prefix }}" data-changer-url="{{ metadata_list.url_for_set_value(item.fields.id) }}"
|
||||
|
@ -17,6 +17,8 @@
|
||||
{% if not config['HIDE_TABLE_DAMAGED_PARTS'] %}
|
||||
data-has-damaged="{{ (item.fields.total_damaged > 0) | int }}" data-damaged="{{ item.fields.total_damaged }}"
|
||||
{% endif %}
|
||||
{% if item.fields.purchase_date is not none %}data-purchase-date="{{ item.fields.purchase_date }}"{% endif %}
|
||||
{% if item.fields.purchase_price is not none %}data-purchase-price="{{ item.fields.purchase_price }}"{% endif %}
|
||||
data-has-purchase-location="{{ item.fields.purchase_location is not none | int }}"
|
||||
{% if item.fields.purchase_location is not none %}
|
||||
data-purchase-location="{{ item.fields.purchase_location }}"
|
||||
@ -68,8 +70,10 @@
|
||||
{{ badge.owner(item, owner, solo=solo, last=last) }}
|
||||
{% endfor %}
|
||||
{{ badge.storage(item, brickset_storages, solo=solo, last=last) }}
|
||||
{{ badge.purchase_location(item, brickset_purchase_locations, solo=solo, last=last) }}
|
||||
{% if not last %}
|
||||
{{ badge.purchase_date(item.purchase_date(), solo=solo, last=last) }}
|
||||
{{ badge.purchase_location(item, brickset_purchase_locations, solo=solo, last=last) }}
|
||||
{{ badge.purchase_price(item.purchase_price(), solo=solo, last=last) }}
|
||||
{% if not solo %}
|
||||
{{ badge.instructions(item, solo=solo, last=last) }}
|
||||
{% endif %}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% if g.login.is_authenticated() %}
|
||||
{{ accordion.header('Management', 'set-management', 'set-details', icon='settings-4-line', class='p-0') }}
|
||||
{{ accordion.header('Management', 'set-management', 'set-details', icon='settings-4-line', class='p-0', expanded=true) }}
|
||||
{{ accordion.header('Owners', 'owner', 'set-management', icon='group-line', class='p-0') }}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% if brickset_owners | length %}
|
||||
@ -14,12 +14,23 @@
|
||||
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.admin', open_owner=true) }}"><i class="ri-settings-4-line"></i> Manage the set owners</a>
|
||||
</div>
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.header('Purchase location', 'purchase-location', 'set-management', icon='building-line') }}
|
||||
{{ accordion.header('Purchase', 'purchase', 'set-management', icon='wallet-3-line', expanded=true) }}
|
||||
<div class="alert alert-info" role="alert">The expected date format here is <code>yyyy/mm/dd</code> (year/month/day), but you can configured how it is displayed in the set card with the <code>PURCHASE_DATE_FORMAT</code> variable.</div>
|
||||
<div class="row row-cols-lg-auto g-1 justify-content-start align-items-center pb-2">
|
||||
<div class="col-12">
|
||||
{{ form.input('Date', item.fields.id, 'date', item.url_for_purchase_date(), item.purchase_date(standard=true), icon='calendar-line', date=true) }}
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
{{ form.input('Price', item.fields.id, 'price', item.url_for_purchase_price(), item.fields.purchase_price, suffix=config['PURCHASE_CURRENCY'], icon='wallet-3-line') }}
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
{% if brickset_purchase_locations | length %}
|
||||
{{ form.select('Purchase location', item, 'purchase_location', brickset_purchase_locations, delete=delete) }}
|
||||
{{ form.select('Location', item, 'purchase_location', brickset_purchase_locations, icon='building-line', delete=delete) }}
|
||||
{% else %}
|
||||
<p class="text-center"><i class="ri-error-warning-line"></i> No purchase location found.</p>
|
||||
<i class="ri-error-warning-line"></i> No purchase location found.
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<a href="{{ url_for('admin.admin', open_purchase_location=true) }}" class="btn btn-primary" role="button"><i class="ri-settings-4-line"></i> Manage the set purchase locations</a>
|
||||
{{ accordion.footer() }}
|
||||
|
@ -22,6 +22,10 @@
|
||||
<button id="sort-damaged" type="button" class="btn btn-outline-primary mb-2"
|
||||
data-sort-attribute="damaged" data-sort-desc="true"><i class="ri-error-warning-line"></i><span class="d-none d-xl-inline"> Damaged</span></button>
|
||||
{% endif %}
|
||||
<button id="sort-purchase-date" type="button" class="btn btn-outline-primary mb-2"
|
||||
data-sort-attribute="purchase-date" data-sort-desc="true"><i class="ri-calendar-line"></i><span class="d-none d-xl-inline"> Date</span></button>
|
||||
<button id="sort-purchase-price" type="button" class="btn btn-outline-primary mb-2"
|
||||
data-sort-attribute="purchase-price" data-sort-desc="true"><i class="ri-wallet-3-line"></i><span class="d-none d-xl-inline"> Price</span></button>
|
||||
<button id="sort-clear" type="button" class="btn btn-outline-dark mb-2"
|
||||
data-sort-clear="true"><i class="ri-close-circle-line"></i><span class="d-none d-xl-inline"> Clear</span></button>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user