Makeroom wizard #27
30
admin.py
30
admin.py
|
@ -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
7
app.py
|
@ -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):
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
47
ext.py
47
ext.py
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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 ();
|
|
@ -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);}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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' %}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue