Makeroom wizard #27

Merged
stranck merged 15 commits from drew-dev into stranck-dev 2024-05-20 10:32:09 +00:00
11 changed files with 136 additions and 31 deletions
Showing only changes of commit e34af503ff - Show all commits

View File

@ -118,35 +118,20 @@ async def room_wizard(request, order:Order):
roomless_orders = {key:value for key,value in orders.items() if not value.room_id and not value.daily}
# Result map
result_map = {
'A':{
'type': 'add_existing',
'to_add': ['b', 'c']
},
'B':{
'type': 'new',
'room_type': 5,
'room_name': 'generated 1',
'to_add': ['B', 'a', 'c']
},
'rooms_to_delete': []
}
result_map = {}
# Get room quotas
room_quota_map = {}
# Check overflows
room_quota_overflow = {}
for key, value in ITEM_VARIATIONS_MAP['bed_in_room'].items():
room_quota = get_quota(ITEMS_ID_MAP['bed_in_room'], value)
capacity = ROOM_CAPACITY_MAP[key] if key in ROOM_CAPACITY_MAP else 1
room_quota_map[value] = math.ceil((len(list(filter(lambda y: y.bed_in_room == value, orders.values())))) / capacity)
current_quota = len(list(filter(lambda y: y.bed_in_room == value and y.room_owner == True, orders.values())))
room_quota_overflow[value] = current_quota - (room_quota_map[value] if value in room_quota_map else 0)
room_quota_overflow[value] = current_quota - int(room_quota.size / capacity) if room_quota else 0
# Init rooms to remove
result_map["void"] = []
# Dismember rooms that are over quota
# Remove rooms that are over quota
for room_type, overflow_qty in {key:value for key,value in room_quota_overflow.items() if value > 0}.items():
sorted_rooms = sorted(incomplete_orders.values(), key=lambda r: len(r.room_members))
for room_to_remove in sorted_rooms[:overflow_qty]:
@ -215,6 +200,13 @@ async def room_wizard(request, order:Order):
tpl = request.app.ctx.tpl.get_template('wizard.html')
return html(tpl.render(order=order, all_orders=all_orders, unconfirmed_orders=orders, data=result_map, jsondata=json.dumps(result_map, skipkeys=True, ensure_ascii=False)))
@bp.post('/room/wizard/submit')
async def submin_from_room_wizard(request:Request, order:Order):
'''Will apply changes to the rooms'''
print(request.body)
return text('Not implemented', status=500)
@bp.get('/propic/remind')
async def propic_remind_missing(request, order:Order):
await clear_cache(request, order)

7
app.py
View File

@ -49,7 +49,7 @@ async def handleException(request, exception):
statusCode = exception.status_code if hasattr(exception, 'status_code') else 500
try:
tpl = app.ctx.tpl.get_template('error.html')
r = html(tpl.render(exception=exception, status_code=statusCode))
r = html(tpl.render(exception=exception, status_code=statusCode), status=statusCode)
except:
traceback.print_exc()
@ -98,6 +98,11 @@ async def gen_barcode(request, code):
return raw(img.getvalue(), content_type="image/png")
@app.route("/manage/lol")
async def lol(request: Request):
await get_quotas(request)
return text('hi')
@app.route(f"/{ORGANIZER}/{EVENT_NAME}/order/<code>/<secret>/open/<secret2>")
async def redirect_explore(request, code, secret, order: Order, secret2=None):

View File

@ -65,6 +65,9 @@ SPONSORSHIP_COLOR_MAP = {
'normal': (142, 36, 170)
}
# Quotes
QUOTES_LIST = []
# Maps Products metadata name <--> ID
ITEMS_ID_MAP = {
'early_bird_ticket': None,

51
ext.py
View File

@ -260,6 +260,32 @@ class Order:
to_return = f"{to_return} [ members = {self.room_members} ]"
return to_return
@dataclass
class Quota:
def __init__(self, data):
self.items = data['items'] if 'items' in data else []
self.variations = data['variations'] if 'variations' in data else []
self.available = data['available'] if 'available' in data else False
self.size = data['size'] if 'size' in data else 0
self.available_number = data['available_number'] if 'available_number' in data else 0
def has_item (self, id: int=-1, variation: int=None):
return id in self.items if not variation else (id in self.items and variation in self.variations)
def get_left (self):
return self.available_number
def __repr__(self):
return f'Quota [items={self.items}, variations={self.variations}] [{self.available_number}/{self.size}]'
def __str__(self):
return f'Quota [items={self.items}, variations={self.variations}] [{self.available_number}/{self.size}]'
def get_quota(item: int, variation: int = None) -> Quota:
for q in QUOTA_LIST:
if (q.has_item(item, variation)): return q
return None
@dataclass
class Quotas:
def __init__(self, data):
@ -277,6 +303,21 @@ async def get_quotas(request: Request=None):
return Quotas(res)
async def load_item_quotas() -> bool:
global QUOTA_LIST
QUOTA_LIST = []
logger.info ('[QUOTAS] Loading quotas...')
success = True
try:
res = await pretixClient.get('quotas/?order=id&with_availability=true')
res = res.json()
for quota_data in res['results']:
QUOTA_LIST.append (Quota(quota_data))
except Exception:
logger.warning(f"[QUOTAS] Error while loading quotas.\n{traceback.format_exc()}")
success = False
return success
async def get_order(request: Request=None):
await request.receive_body()
return await request.app.ctx.om.get_order(request=request)
@ -340,6 +381,12 @@ class OrderManager:
logger.error("[CACHE] Questions were not loading correctly. Aborting filling cache...")
return False
# Load quotas
r = await load_item_quotas()
if(not r and check_itemsQuestions):
logger.error("[CACHE] Quotas were not loading correctly. Aborting filling cache...")
return False
cache = {}
orderList = []
success = True
@ -375,7 +422,7 @@ class OrderManager:
asyncio.create_task(validate_rooms(None, rooms, self))
return success
async def get_order(self, request=None, code=None, secret=None, nfc_id=None, cached=False):
# if it's a nfc id, just retorn it
@ -417,4 +464,4 @@ class OrderManager:
if request and secret != res['secret']:
raise exceptions.Forbidden("Your session has expired due to a token change. Please check your E-Mail for an updated link!")
return order
return order

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" /></svg>

View File

@ -4,6 +4,8 @@ var draggingData = {
parentRoomId: 0
}
var allowRedirect = false;
function initObjects (){
draggables = document.querySelectorAll("div.grid.people div.edit-drag");
rooms = document.querySelectorAll("main.container>div.room");
@ -117,7 +119,7 @@ function getData () { return draggingData; }
// This default onbeforeunload event
window.onbeforeunload = function(){
return "Any changes to the rooms will be discarded."
if (!allowRedirect) return "Any changes to the rooms will be discarded."
}
/* Model managing */
@ -152,4 +154,44 @@ function onSave (){
}
}
/**
*
* @param {Element} element
*/
function submitData (element){
if (element.ariaDisabled) return;
element.ariaDisabled = true;
element.setAttribute("aria-busy", true);
document.querySelector("#modalClose").setAttribute("disabled", true);
document.querySelector("#modalClose").style.display = 'none';
// Create request
const xhr = new XMLHttpRequest();
xhr.open('POST', '/manage/admin/room/wizard/submit', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
let popoverText = document.querySelector("#popover-status-text");
let popoverStatus = document.querySelector("#popover-status");
popoverStatus.classList.remove('status-error');
popoverStatus.classList.remove('status-success');
if (xhr.status === 200) {
// Handle correct redirect
popoverText.innerText = "Changes applied successfully. Redirecting..."
popoverStatus.classList.add('status-success');
} else {
// Handle errors
let error = xhr.statusText;
popoverText.innerText = "Could not apply changes: " + error;
console.error('Error submitting data:', error);
popoverStatus.classList.add('status-error');
}
popoverStatus.showPopover();
allowRedirect = true;
setTimeout(()=>window.location.assign('/manage/admin'), 3000);
}
};
xhr.send(JSON.stringify(model));
}
initObjects ();

View File

@ -27,6 +27,14 @@ summary:has(span.status) {
100% { background-position:57.75% 0%; }
}
/* Popover */
*[popover]:popover-open {
border-radius: var(--border-radius);
border: 1px solid #fff;
backdrop-filter: blur(10px);
padding: 1rem;
}
/* Dark theme */
@media only screen and (prefers-color-scheme: dark) {
.icon {filter: invert(1);}

View File

@ -50,6 +50,14 @@ div.room:nth-child(2n) {
float: right;
}
.status-success {
background-color: #2e9147aa;
}
.status-error {
background-color: #912e2eaa;
}
/* Dark theme */
@media only screen and (prefers-color-scheme: dark) {
div.drag-over {

View File

@ -19,10 +19,10 @@
<p>Rooms</p>
<a href="/manage/nosecount" role="button" title="Shortcut to the nosecount's admin data">Manage rooms</a>
<a href="/manage/admin/room/verify" role="button" title="Will unconfirm rooms that fail the default check. Useful when editing answers from Pretix">Verify Rooms</a>
<a href="/manage/admin/room/wizard" role="button" title="Will try matching roomless furs together, you can still re-arrange the users before confirming.">Fill Rooms</a>
<a href="/manage/admin/room/wizard" role="button" title="Auto fill unconfirmed rooms. You can review and edit matches before confirming.">Fill Rooms</a>
<hr>
<p>Profiles</p>
<a href="#" onclick="confirmAction('propicReminder', this)" role="button" title="Will remind via mail all people who event uploaded a badge to do it" action="/manage/admin/propic/remind">Remind badge upload</a>
<a href="#" onclick="confirmAction('propicReminder', this)" role="button" title="Will email all people who haven't uploaded a badge, yet" action="/manage/admin/propic/remind">Remind badge upload</a>
<a href="/manage/admin/room/autoconfirm" role="button" title="Will confirm all the full rooms that are still unconfirmed">Auto-confirm Rooms</a>
<hr>
{% include 'components/confirm_action_modal.html' %}

View File

@ -16,12 +16,13 @@
unconfirmed_orders = all non confirmed rooms orders
all_orders = all orders
data = assigned rooms -->
<h2>Review rooms</h2>
<h2>Review rooms <a href="#popover-empty-room-tip" onclick="document.querySelector('#popover-wizard-tip').showPopover()">?</a></h2>
<div popover id="popover-wizard-tip">This is the preview page. Re-arrange users by dragging and dropping them in the rooms.<br>Once finished, scroll down to either <i>'Confirm'</i> changes or <i>'Undo'</i> them.</div>
<hr>
{% for room in data.items() %}
{% if room[0] in all_orders %}
{%with room_order = unconfirmed_orders[room[0]] %}
<div class="room" id="room-{{room_order.code}}" room-type="{{room_order.bed_in_room}}" room-size="{{len(room[1]['to_add'])}}" current-size="{{len(room[1]['to_add'])}}">
<div class="room" id="room-{{room_order.code}}" room-type="{{room_order.bed_in_room}}" room-size="{{room_order.room_person_no - len(room_order.room_members)}}" current-size="{{len(room[1]['to_add'])}}">
<h4 style="margin-top:1em;">
<span>{{room_order.room_name if room_order.room_name else room[1]['room_name'] if room[1] and room[1]['room_name'] else ''}}</span>
</h4>
@ -65,14 +66,15 @@
<dialog id="modalConfirmDialog">
<article>
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
<a href="#close" id="modalClose" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
<h3 id="intentText">Confirm arrangement?</h3>
<p id="intentDescription">
Roomless guests will be moved around existing rooms and newly generated ones.<br>
This will also confirm all rooms.
</p>
<div popover id="popover-status"><span id="popover-status-text"></span></div>
<footer>
<input id="intentSend" type="submit" value="Confirm" />
<button id="intentSend" onclick="submitData(this)">Confirm</button>
</footer>
</article>
</dialog>

View File

@ -29,7 +29,6 @@ QUESTION_TYPES = { #https://docs.pretix.eu/en/latest/api/resources/questions.htm
}
TYPE_OF_QUESTIONS = {} # maps questionId -> type
async def load_questions() -> bool:
global TYPE_OF_QUESTIONS
# TYPE_OF_QUESTIONS.clear() It should not be needed