Makeroom wizard #27
16
admin.py
16
admin.py
|
@ -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):
|
||||||
|
|
|
@ -28,28 +28,4 @@ function confirmAction (intent, sender) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
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 (){
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
let newParentContainer = newParent.querySelector('div.grid.people')
|
if (moveToRoom (data.id, data.parentRoomId.replace('room-',''), newParent.id.replace('room-','')) === true) {
|
||||||
newParentContainer.appendChild (item);
|
let newParentContainer = newParent.querySelector('div.grid.people')
|
||||||
oldParent.setAttribute("current-size", parseInt(oldParent.getAttribute("current-size") - 1))
|
newParentContainer.appendChild (item);
|
||||||
newParent.setAttribute("current-size", parseInt(newParent.getAttribute("current-size") + 1))
|
let oldParentQty = parseInt(oldParent.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 ();
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,15 +19,8 @@
|
||||||
<img src="/res/furizon-light.png" style="height:4rem;text-align:center;">
|
<img src="/res/furizon-light.png" style="height:4rem;text-align:center;">
|
||||||
</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 %}
|
||||||
|
|
|
@ -19,44 +19,68 @@
|
||||||
<h2>Review rooms</h2>
|
<h2>Review rooms</h2>
|
||||||
<hr>
|
<hr>
|
||||||
{% for room in data.items() %}
|
{% for room in data.items() %}
|
||||||
{%with room_order = unconfirmed_orders[room[0]] %}
|
{% if room[0] in all_orders %}
|
||||||
<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'])}}">
|
{%with room_order = unconfirmed_orders[room[0]] %}
|
||||||
<h4 style="margin-top:1em;">
|
<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'])}}">
|
||||||
<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 style="margin-top:1em;">
|
||||||
</h4>
|
<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>
|
||||||
<div class="grid people" style="padding-bottom:1em;">
|
</h4>
|
||||||
{% for m in room_order.room_members %}
|
<div class="grid people" style="padding-bottom:1em;">
|
||||||
{% if m in all_orders %}
|
{% for m in room_order.room_members %}
|
||||||
{% with person = all_orders[m] %}
|
{% if m in all_orders %}
|
||||||
<div class="edit-disabled" style="margin-bottom: 1em;">
|
{% with person = all_orders[m] %}
|
||||||
{% with current=None, order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
<div class="edit-disabled" style="margin-bottom: 1em;">
|
||||||
{% include 'blocks/propic.html' %}
|
{% with current=None, order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
||||||
{% endwith %}
|
{% include 'blocks/propic.html' %}
|
||||||
<h5>{{person.ans('fursona_name')}}</h5>
|
{% endwith %}
|
||||||
</div>
|
<h5>{{person.ans('fursona_name')}}</h5>
|
||||||
{% endwith %}
|
</div>
|
||||||
{% endif %}
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
{% for m in room[1]['to_add'] %}
|
{% endfor %}
|
||||||
{% if m in unconfirmed_orders %}
|
{% for m in room[1]['to_add'] %}
|
||||||
{% with person = unconfirmed_orders[m] %}
|
{% if m in unconfirmed_orders %}
|
||||||
<div class="edit-drag" id="{{person.code}}" room-type="{{person.bed_in_room}}" style="margin-bottom: 1em;" draggable="true">
|
{% with person = unconfirmed_orders[m] %}
|
||||||
{% with current=None, order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
<div class="edit-drag" id="{{person.code}}" room-type="{{person.bed_in_room}}" style="margin-bottom: 1em;" draggable="true">
|
||||||
{% include 'blocks/propic.html' %}
|
{% with current=None, order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
||||||
{% endwith %}
|
{% include 'blocks/propic.html' %}
|
||||||
<h5>{{person.ans('fursona_name')}}</h5>
|
{% endwith %}
|
||||||
</div>
|
<h5>{{person.ans('fursona_name')}}</h5>
|
||||||
{% endwith %}
|
</div>
|
||||||
{% endif %}
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</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 %}
|
||||||
|
|
Loading…
Reference in New Issue