Makeroom wizard #27

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

View File

@ -8,6 +8,8 @@ from io import StringIO
from sanic.log import logger from sanic.log import logger
import csv import csv
import time import time
import json
import math
bp = Blueprint("admin", url_prefix="/manage/admin") bp = Blueprint("admin", url_prefix="/manage/admin")
@ -108,7 +110,7 @@ async def room_wizard(request, order:Order):
await clear_cache(request, order) await clear_cache(request, order)
#Separate orders which have incomplete rooms and which have no rooms #Separate orders which have incomplete rooms and which have no rooms
all_orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: len(x[1].room_members), reverse=True) if value.status not in ['c', 'e']} all_orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: len(x[1].room_members), reverse=True) if value.status not in ['c', 'e'] and not value.daily}
orders = {key:value for key,value in sorted(all_orders.items(), key=lambda x: x[1].ans('fursona_name')) if not value.room_confirmed} orders = {key:value for key,value in sorted(all_orders.items(), key=lambda x: x[1].ans('fursona_name')) if not value.room_confirmed}
# Orders with incomplete rooms # Orders with incomplete rooms
incomplete_orders = {key:value for key,value in orders.items() if value.code == value.room_id and (value.room_person_no - len(value.room_members)) > 0} incomplete_orders = {key:value for key,value in orders.items() if value.code == value.room_id and (value.room_person_no - len(value.room_members)) > 0}
@ -131,6 +133,15 @@ async def room_wizard(request, order:Order):
result_map = {} result_map = {}
# Get room quotas
room_quota_map = {}
for key, value in ITEM_VARIATIONS_MAP['bed_in_room'].items():
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)
print('RMQ = ')
print(room_quota_map)
# Fill already existing rooms # Fill already existing rooms
for room_order in incomplete_orders.items(): for room_order in incomplete_orders.items():
room = room_order[1] room = room_order[1]
@ -185,8 +196,9 @@ async def room_wizard(request, order:Order):
'to_add': to_add 'to_add': to_add
} }
result_map["infinite"] = { 'to_add': [] }
tpl = request.app.ctx.tpl.get_template('wizard.html') 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)) 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.get('/propic/remind') @bp.get('/propic/remind')
async def propic_remind_missing(request, order:Order): async def propic_remind_missing(request, order:Order):

View File

@ -29,27 +29,3 @@ function confirmAction (intent, sender) {
} }
document.getElementById('modalOrderEditDialog').setAttribute('open', 'true'); document.getElementById('modalOrderEditDialog').setAttribute('open', 'true');
} }
function setLoading (){
document.getElementById('loadingIndicator').style.visibility = 'unset';
}
function setLoaded (){
document.getElementById('loadingIndicator').style.visibility = 'hidden';
}
async function roomGuestEditAction (intent, sender) {
if (['new', 'fromOrder'].includes (intent) == false) return
setLoaded ();
document.getElementById('roomGuestEditorDialog').setAttribute('open', 'true');
}
async function retrieveUsers (type) {
setLoading();
setLoaded();
}
function adaptRoomFormat (){
}

View File

@ -82,15 +82,22 @@ function drop(e) {
let item = document.getElementById (data.id) let item = document.getElementById (data.id)
let oldParent = document.getElementById (data.parentRoomId) let oldParent = document.getElementById (data.parentRoomId)
let newParent = e.target; let newParent = e.target;
if (moveToRoom (data.id, data.parentRoomId.replace('room-',''), newParent.id.replace('room-','')) === true) {
let newParentContainer = newParent.querySelector('div.grid.people') let newParentContainer = newParent.querySelector('div.grid.people')
newParentContainer.appendChild (item); newParentContainer.appendChild (item);
oldParent.setAttribute("current-size", parseInt(oldParent.getAttribute("current-size") - 1)) let oldParentQty = parseInt(oldParent.getAttribute("current-size")) - 1;
newParent.setAttribute("current-size", parseInt(newParent.getAttribute("current-size") + 1)) let newParentQty = parseInt(newParent.getAttribute("current-size")) + 1;
let newParentCapacity = parseInt(newParent.getAttribute("room-size"));
oldParent.setAttribute("current-size", oldParentQty);
newParent.setAttribute("current-size", newParentQty);
oldParent.classList.remove('complete');
if (newParentCapacity == newParentQty) newParent.classList.add('complete');
}
} }
} }
function toggleRoomSelection(newStatus) { function toggleRoomSelection(newStatus) {
rooms = document.querySelectorAll("main.container>div.room"); rooms = document.querySelectorAll("div.room");
Array.from(rooms).forEach(room=>{ Array.from(rooms).forEach(room=>{
room.classList.toggle('interactless', newStatus); room.classList.toggle('interactless', newStatus);
room.classList.remove('drag-over'); room.classList.remove('drag-over');
@ -104,12 +111,45 @@ function setData (id, roomType, parentRoomId) {
draggingData.parentRoomId = parentRoomId; draggingData.parentRoomId = parentRoomId;
} }
function resetData (){ function resetData (){ setData(0, 0, 0); }
setData(0, 0, 0);
function getData () { return draggingData; }
// This default onbeforeunload event
window.onbeforeunload = function(){
return "Any changes to the rooms will be discarded."
} }
function getData () { /* Model managing */
return draggingData;
var model = saveData;
const toAdd = "to_add";
function moveToRoom (order, from, to){
if (!model) { console.error("Model is null", order, from, to); return false; }
if (!model[from]) { console.error("Parent is null", order, from, to); return false; }
if (!model[to]) { console.error("Destination is null", order, from, to); return false; }
if (!model[from][toAdd] || !model[from][toAdd].includes(order)) { console.error("Order is not in parent", order, from, to); return false; }
if (!model[to][toAdd]) model[to][toAdd] = [];
// Delete order from the original room
model[from][toAdd] = model[from][toAdd].filter (itm=> itm !== order)
// Add it to the destination room
model[to][toAdd].push (order);
return true;
}
function onSave (){
if (model['infinite'] && model['infinite'][toAdd] && model['infinite'][toAdd].length > 0) {
setTimeout(()=>{
let roomItem = document.querySelector("#room-infinite");
roomItem.scrollIntoView();
roomItem.classList.add('drag-forbidden');
setTimeout(()=>roomItem.classList.remove('drag-forbidden'), 3000);
}, 100);
} else {
document.getElementById('modalConfirmDialog').setAttribute('open', 'true');
}
} }
initObjects (); initObjects ();

View File

@ -10,30 +10,63 @@ div.grid.people div.edit-drag>div.propic-container {
} }
div.drag-over { div.drag-over {
border-radius: 1rem; border-color: #000;
border: 0.2rem dashed #000; border-style: dashed;
} }
div.drag-forbidden { div.drag-forbidden {
border-color: #c92121aa; border-color: #c92121aa;
} }
div.interactless>*{ div.interactless > * {
pointer-events: none; pointer-events: none;
} }
div.room.complete {
border-color: #21c929aa;
border-style: solid;
}
div.room {
border-radius: var(--border-radius);
border: 1px solid transparent;
margin-bottom: var(--spacing);
}
div.room > h4 { div.room > h4 {
user-select: none; user-select: none;
margin-left: 1.6rem;
}
div.room:nth-child(2n) {
background-color: #ffffff55;
}
div.room:nth-child(2n) {
background-color: #cccccc55;
}
.align-right {
float: right;
} }
/* Dark theme */ /* Dark theme */
@media only screen and (prefers-color-scheme: dark) { @media only screen and (prefers-color-scheme: dark) {
div.drag-over { div.drag-over {
border-radius: 1rem; border-color: #fff;
border: 0.2rem dashed #fff; border-style: dashed;
} }
div.drag-forbidden { div.drag-forbidden {
border-color: #c92121aa; border-color: #c92121aa;
} }
div.room {
background-color: #16161655;
}
div.room:nth-child(2n) {
background-color: #20202055;
}
} }

View File

@ -20,14 +20,7 @@
</picture> </picture>
</header> </header>
{% if order and order.isAdmin() %}
<div class="room-actions admin-actions-header">
<a onclick="roomGuestEditAction('new', this)" action="/manage/admin/{{order.code}}"><img src="/res/icons/book-plus.svg" class="icon" /><span>New room</span></a>
</div>
{% else %}
<p>Welcome to the nosecount page! Here you can see all of the available rooms at the convention, as well as the occupants currently staying in each room. Use this page to find your friends and plan your meet-ups.</p> <p>Welcome to the nosecount page! Here you can see all of the available rooms at the convention, as well as the occupants currently staying in each room. Use this page to find your friends and plan your meet-ups.</p>
{% endif %}
{% if filtered and order %} {% if filtered and order %}
{% for person in filtered.values() %} {% for person in filtered.values() %}
@ -56,7 +49,6 @@
<span>{{o.room_name}}</span> <span>{{o.room_name}}</span>
{% if order and order.isAdmin() %} {% if order and order.isAdmin() %}
<div class="room-actions"> <div class="room-actions">
<a onclick="roomGuestEditAction('fromOrder', this)" action="/manage/admin/{{o.code}}"><img src="/res/icons/users.svg" class="icon" /><span>Add members</span></a>
<a onclick="confirmAction('rename', this)" action="/manage/admin/room/rename/{{o.code}}"><img src="/res/icons/pencil.svg" class="icon" /><span>Rename</span></a> <a onclick="confirmAction('rename', this)" action="/manage/admin/room/rename/{{o.code}}"><img src="/res/icons/pencil.svg" class="icon" /><span>Rename</span></a>
<a onclick="confirmAction('unconfirm', this)" action="/manage/admin/room/unconfirm/{{o.code}}"><img src="/res/icons/door_open.svg" class="icon" /><span>Unconfirm</span></a> <a onclick="confirmAction('unconfirm', this)" action="/manage/admin/room/unconfirm/{{o.code}}"><img src="/res/icons/door_open.svg" class="icon" /><span>Unconfirm</span></a>
<a class="act-del" onclick="confirmAction('delete', this)" action="/manage/admin/room/delete/{{o.code}}"><img src="/res/icons/delete.svg" class="icon" /><span>Delete</span></a> <a class="act-del" onclick="confirmAction('delete', this)" action="/manage/admin/room/delete/{{o.code}}"><img src="/res/icons/delete.svg" class="icon" /><span>Delete</span></a>
@ -160,18 +152,5 @@
</article> </article>
</dialog> </dialog>
</form> </form>
<form id="roomFormAction" method="GET" action=""></form>
<dialog id="roomGuestEditorDialog">
<article>
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
<h3 id="intentText"><span>Room editor<img id="loadingIndicator" src="/res/icons/loading.svg" class="icon spin" /></span></h3>
<div class="room-members-container">
</div>
<input type="hidden" name="orders">
</article>
</dialog>
</form>
</main> </main>
{% endblock %} {% endblock %}

View File

@ -19,6 +19,7 @@
<h2>Review rooms</h2> <h2>Review rooms</h2>
<hr> <hr>
{% for room in data.items() %} {% for room in data.items() %}
{% if room[0] in all_orders %}
{%with room_order = unconfirmed_orders[room[0]] %} {%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="{{len(room[1]['to_add'])}}" current-size="{{len(room[1]['to_add'])}}">
<h4 style="margin-top:1em;"> <h4 style="margin-top:1em;">
@ -52,11 +53,34 @@
</div> </div>
</div> </div>
{% endwith %} {% endwith %}
{% endif %}
{% endfor %} {% endfor %}
<div class="room" infinite="true" id="room-infinite" room-size="999999999" current-size="0"> <div class="room" infinite="true" id="room-infinite" room-size="999999999" current-size="0">
<h2>Empty room</h2> <h4 style="margin-top:1em;">Empty room <a href="#popover-empty-room-tip" onclick="document.querySelector('#popover-empty-room-tip').showPopover()">?</a></button></h4>
<div popover id="popover-empty-room-tip">This is a placeholder room. Place users temporarily in order to free space and arrange rooms</div>
<div class="grid people" style="padding-bottom:1em;"></div> <div class="grid people" style="padding-bottom:1em;"></div>
</div> </div>
<a href="/manage/admin" role="button" title="Discard all changes and go back to the admin page">Undo</a>
<a href="#" class="align-right" onclick="onSave()" role="button" title="Will try saving current changes.">Confirm changes</a>
<dialog id="modalConfirmDialog">
<article>
<a href="#close" 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>
<footer>
<input id="intentSend" type="submit" value="Confirm" />
</footer>
</article>
</dialog>
<script type="text/javascript">
let saveData = JSON.parse('{{jsondata|safe}}');
</script>
</main> </main>
{% endblock %} {% endblock %}