Commit 8fdb8ed2 by Tuomas Riihimäki

Merge branch 'reserve_ui' into 'master'

Ilmoitus kun lähtee poies sivulta

Ilimoitus kun lähtee pois varaussivulta, nyt enemmän selainystävällisenä.

(vanha tyyli toimi joissain selaimissa, mut nykyspeksin mukaan ei voida näyttää alerttia beforeunload -eventissä, vaan voidaan pelkästään palauttaa viesti, joka sitten näytetään käyttäjälle selaimen puolesta).

See merge request !175
2 parents dbd353de aeb5d833
...@@ -117,6 +117,27 @@ public class PlaceFacade extends IntegerPkGenericFacade<Place> { ...@@ -117,6 +117,27 @@ public class PlaceFacade extends IntegerPkGenericFacade<Place> {
return q.getResultList(); return q.getResultList();
} }
public List<Place> findUsersUnlocketSelected(EventUser user) {
CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Place> cq = cb.createQuery(Place.class);
Root<Place> root = cq.from(Place.class);
cq.select(root);
cq.where(
cb.and(
cb.equal(root.get(Place_.map).get(EventMap_.event), user.getEvent()),
cb.equal(root.get(Place_.currentUser), user),
cb.isNull(root.get(Place_.group))
));
TypedQuery<Place> q = getEm().createQuery(cq);
return q.getResultList();
}
public int setBuyable(EventMap map, String like, boolean b) { public int setBuyable(EventMap map, String like, boolean b) {
CriteriaBuilder cb = getEm().getCriteriaBuilder(); CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Place> cq = cb.createQuery(Place.class); CriteriaQuery<Place> cq = cb.createQuery(Place.class);
......
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" <html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core" xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html" xmlns:h="http://java.sun.com/jsf/html"
xmlns:map="http://java.sun.com/jsf/composite/cditools/map" xmlns:map="http://java.sun.com/jsf/composite/cditools/map"
xmlns:tools="http://java.sun.com/jsf/composite/cditools" xmlns:tools="http://java.sun.com/jsf/composite/cditools"
xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:p="http://primefaces.org/ui"> xmlns:p="http://primefaces.org/ui">
<h:body> <h:body>
<ui:composition template="#{sessionHandler.template}"> <ui:composition template="#{sessionHandler.template}">
<f:metadata> <f:metadata>
<f:event type="preRenderView" <f:event type="preRenderView" listener="#{ajaxMapView.initReserveMap()}"/>
listener="#{ajaxMapView.initReserveMap()}" /> </f:metadata>
</f:metadata> <ui:param name="thispage" value="page.place.placemap"/>
<ui:param name="thispage" value="page.place.placemap" /> <ui:define name="content">
<ui:define name="content"> <h:form>
<h:form> <p:remoteCommand name="updateWholePage" update="@all" action="#{ajaxMapView.initReserveMap()}"/>
<p:remoteCommand name="updateWholePage" update="@all" </h:form>
action="#{ajaxMapView.initReserveMap()}" /> <p:dialog rendered="#{ajaxMapView.isMgmtPermission()}" visible="#{!empty ajaxMapView.place}" id="fbdiag">
</h:form> Clicked place name : #{ajaxMapView.place.name};
<p:dialog rendered="#{ajaxMapView.isMgmtPermission()}" <h:link rendered="#{!empty ajaxMapView.place}" outcome="/place/edit">
visible="#{!empty ajaxMapView.place}" id="fbdiag"> <f:param name="placeid" value="#{ajaxMapView.place.id}"/>
Clicked place name : #{ajaxMapView.place.name}; Muokkaa
<h:link rendered="#{!empty ajaxMapView.place}" outcome="/place/edit"> </h:link>
<f:param name="placeid" value="#{ajaxMapView.place.id}" /> </p:dialog>
Muokkaa
</h:link> <h:outputScript target="head" library="seatjs" name="d3.min.js"/>
</p:dialog> <h:outputScript target="head" library="seatjs" name="d3-tip.js"/>
<h:outputScript target="head" library="seatjs" name="seatmap.js"/>
<h:outputScript target="head" library="seatjs" name="d3.min.js" /> <h:outputStylesheet library="seatjs" name="placemap.css"/>
<h:outputScript target="head" library="seatjs" name="d3-tip.js" />
<h:outputScript target="head" library="seatjs" name="seatmap.js" />
<h:outputStylesheet library="seatjs" name="placemap.css" /> <ui:fragment rendered="#{layoutView.manageContent}">
<h:link rendered="#{layoutView.manageContent}" styleClass="editorlink" value="Edit content for users in queue" outcome="/pages/manage">
<f:param name="pagename" value="#{layoutView.pagepath}:inqueue"/>
<ui:fragment rendered="#{layoutView.manageContent}"> </h:link>
<h:link rendered="#{layoutView.manageContent}" <br/>
styleClass="editorlink" value="Edit content for users in queue"
outcome="/pages/manage"> <h:link rendered="#{layoutView.manageContent}" styleClass="editorlink" value="Edit content for user reserving places" outcome="/pages/manage">
<f:param name="pagename" value="#{layoutView.pagepath}:inqueue" /> <f:param name="pagename" value="#{layoutView.pagepath}:reserving"/>
</h:link> </h:link>
<br /> <br/>
</ui:fragment>
<h:link rendered="#{layoutView.manageContent}"
styleClass="editorlink"
value="Edit content for user reserving places" <ui:repeat var="cont1" rendered="#{ajaxMapView.queueEnabled and ajaxMapView.reserving}" value="#{menuView.getPagecontent('reserving')}">
outcome="/pages/manage"> <h:outputText value="#{cont1.content}" escape="false"/>
<f:param name="pagename" value="#{layoutView.pagepath}:reserving" /> </ui:repeat>
</h:link>
<br />
</ui:fragment> <ui:repeat var="cont1" rendered="#{ajaxMapView.queueEnabled and not ajaxMapView.reserving}" value="#{menuView.getPagecontent('inqueue')}">
<h:outputText value="#{cont1.content}" escape="false"/>
</ui:repeat>
<ui:repeat var="cont1" <br/>
rendered="#{ajaxMapView.queueEnabled and ajaxMapView.reserving}"
value="#{menuView.getPagecontent('reserving')}"> <h3>
<h:outputText value="#{cont1.content}" escape="false" /> <h:outputText value="#{i18n['mapView.yourPlaces']}"/>
</ui:repeat> </h3>
<p:dataTable tableStyle=" width: auto;" var="cnt" value="#{ajaxMapView.slotcount}">
<ui:repeat var="cont1" <p:column headerText="#{i18n['mapView.productcount.productname']}">
rendered="#{ajaxMapView.queueEnabled and not ajaxMapView.reserving}" <h:outputText value="#{cnt.product.name}"/>
value="#{menuView.getPagecontent('inqueue')}"> </p:column>
<h:outputText value="#{cont1.content}" escape="false" /> <p:column headerText="#{i18n['mapView.productcount.productcount']}">
</ui:repeat> <h:outputText value="#{cnt.count}"/>
<br /> </p:column>
<h3> </p:dataTable>
<h:outputText value="#{i18n['mapView.yourPlaces']}" /> <ui:fragment rendered="#{ajaxMapView.reserving}">
</h3> <div style="margin: 5px;">
<h:form id="placeselectform">
<p:dataTable tableStyle=" <p:commandButton onclick="$(window).unbind('beforeunload');" rendered="#{ajaxMapView.canUserBuy()}" value="#{i18n['mapView.buyPlaces']}" actionListener="#{ajaxMapView.buySelectedPlaces()}" />
width: auto;" var="cnt" </h:form>
value="#{ajaxMapView.slotcount}"> </div>
</ui:fragment>
<p:column headerText="#{i18n['mapView.productcount.productname']}">
<h:outputText value="#{cnt.product.name}" />
</p:column> <ui:fragment rendered="#{ajaxMapView.queueEnabled and not ajaxMapView.reserving}">
<p:column headerText="#{i18n['mapView.productcount.productcount']}"> <h3><h:outputText value="#{i18n['mapView.youAreInQueue']}"/></h3>
<h:outputText value="#{cnt.count}" />
</p:column> <div style="margin: 1em;">
</p:dataTable> <h:outputText value="#{i18n['mapView.queuePosition']}: "/>
<ui:fragment rendered="#{ajaxMapView.reserving}"> <span id="queuepos"/><br/>
<div style="margin: 5px;"> <h:outputText value=" #{i18n['mapView.queuePositionUpdated']}: "/>
<h:form id="placeselectform"> <span id="queueupdated"/> <br/>
<p:commandButton rendered="#{ajaxMapView.canUserBuy()}" </div>
value="#{i18n['mapView.buyPlaces']}" </ui:fragment>
action="#{ajaxMapView.buySelectedPlaces()}" ajax="false" />
</h:form>
</div> <svg id="seatmap" style="margin: auto; border: 1px solid black;" width="#{ajaxMapView.map.width}px" height="#{ajaxMapView.map.height}px"/>
</ui:fragment>
<script type="text/javascript">
// Queue is enabled and we are reserving.
<ui:fragment var queueReserving = #{(ajaxMapView.queueEnabled and not ajaxMapView.reserving)?"true":"false"};
rendered="#{ajaxMapView.queueEnabled and not ajaxMapView.reserving}">
<h3><h:outputText value="#{i18n['mapView.youAreInQueue']}" /></h3>
<div style="margin: 1em;"> function updateMap() {
<h:outputText value="#{i18n['mapView.queuePosition']}: " /> px.update();
<span id="queuepos" /><br /> reloadQueue();
<h:outputText value=" #{i18n['mapView.queuePositionUpdated']}: " /> }
<span id="queueupdated" /> <br />
</div>
</ui:fragment> function reloadQueue() {
if (queueReserving) {
$.getJSON("#{request.contextPath}/rest/placemap/v1/queue/#{ajaxMapView.map.id}/#{ajaxMapView.eventuser.id}")
<svg id="seatmap" style="margin: auto; border: 1px solid black;" .done(function (data) {
width="#{ajaxMapView.map.width}px" updateQueue(data);
height="#{ajaxMapView.map.height}px" /> }).fail(function () {
location.reload();
});
<script type="text/javascript"> }
// Queue is enabled and we are reserving. }
var queueReserving = #{(ajaxMapView.queueEnabled and not ajaxMapView.reserving)?"true":"false"};
function updateQueue(data) {
function updateMap() { if (data === undefined) {
px.update(); alert('not in queue');
reloadQueue(); return;
} }
if (data.value == 0) {
function reloadQueue(){ updateWholePage();
if(queueReserving){ } else {
$.getJSON("#{request.contextPath}/rest/placemap/v1/queue/#{ajaxMapView.map.id}/#{ajaxMapView.eventuser.id}") $("#queuepos").text(data.value);
.done(function(data){ var d = new Date();
updateQueue(data); $("#queueupdated").text(d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds())
} ).fail(function(){ }
location.reload(); }
});
}
} setInterval(function () {
updateMap()
}, 5000);
function updateQueue(data) {
if(data === undefined) { var pageEscapeAlert = queueReserving;
alert('not in queue');
return; $().ready(function () {
} reloadQueue();
});
if(data.value == 0) {
updateWholePage();
} else { </script>
$("#queuepos").text(data.value);
var d = new Date(); <script type="text/javascript">
$("#queueupdated").text(d.getHours() + ":" + d.getMinutes() + ":"+ d.getSeconds())
}
} px = placemap({
element: document.getElementById("seatmap"),
moyaurl: "#{request.contextPath}",
setInterval(function () { updateMap() }, 5000); map_id: #{ajaxMapView.map.id},
placereserve: true,
var pageEscapeAlert = queueReserving; onclick: function (d) {
// px.update();
$().ready(function(){ //alert(d);
reloadQueue(); // #{ajaxMapView.isMgmtPermission()?'placeClicker([{name: \'placeId\', value: d}])':''}
}); return false;
$(window).bind('beforeunload', function() { }
if(pageEscapeAlert) { });
alert("#{i18n['mapView.lockPlacesBeforeLeaving']}"); // document.getElementById("editbutton").addEventListener("click",
} // function() {
}); // px.enable_edit();
// });
//px.enable_edit();
</script>
</script>
<script type="text/javascript">
<h:panelGrid columns="3" cellpadding="10">
<h:panelGrid columns="2">
<div style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(83, 83, 83); width: 10px; height: 10px;">&nbsp;</div>
px = placemap({ <h:outputText value="#{i18n['placeSelect.legend.grey']}"/>
element : document.getElementById("seatmap"),
moyaurl : "#{request.contextPath}", <div style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(204, 204, 204); width: 10px; height: 10px;">&nbsp;</div>
map_id : #{ajaxMapView.map.id}, <h:outputText value="#{i18n['placeSelect.legend.white']}"/>
placereserve: true,
onclick : function(d) { <div style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(180, 1, 0); width: 10px; height: 10px;">&nbsp;</div>
// px.update(); <h:outputText value="#{i18n['placeSelect.legend.red']}"/>
//alert(d);
// #{ajaxMapView.isMgmtPermission()?'placeClicker([{name: \'placeId\', value: d}])':''} <div style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(47, 165, 37); width: 10px; height: 10px;">&nbsp;</div>
return false; <h:outputText value="#{i18n['placeSelect.legend.green']}"/>
}
}); <div style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(67, 66, 193); width: 10px; height: 10px;">&nbsp;</div>
// document.getElementById("editbutton").addEventListener("click", <h:outputText value="#{i18n['placeSelect.legend.blue']}"/>
// function() { </h:panelGrid>
// px.enable_edit();
// }); <h:panelGrid columnClasses=",rightalign" columns="2">
//px.enable_edit(); <h:outputLabel value="#{i18n['placeSelect.totalPlaces']}:"/>
<h:outputText value="#{ajaxMapView.availablePlaces}"/>
</script>
<h:outputLabel value="#{i18n['placeSelect.placesleft']}:"/>
<h:panelGrid columns="3" cellpadding="10"> <h:outputText value="#{ajaxMapView.placesLeftToSelect}"/>
<h:panelGrid columns="2">
<div
style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(83, 83, 83); width: 10px; height: 10px;">&nbsp;</div> </h:panelGrid>
<h:outputText value="#{i18n['placeSelect.legend.grey']}" />
<div </h:panelGrid>
style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(204, 204, 204); width: 10px; height: 10px;">&nbsp;</div>
<h:outputText value="#{i18n['placeSelect.legend.white']}" /> <ui:fragment rendered="#{ajaxMapView.reserving}">
<div <div style="margin: 5px;">
style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(180, 1, 0); width: 10px; height: 10px;">&nbsp;</div> <h:form id="placeselectformBottom">
<h:outputText value="#{i18n['placeSelect.legend.red']}" /> <p:commandButton onclick="$(window).unbind('beforeunload');" rendered="#{ajaxMapView.canUserBuy()}" value="#{i18n['mapView.buyPlaces']}" actionListener="#{ajaxMapView.buySelectedPlaces()}"/>
<div </h:form>
style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(47, 165, 37); width: 10px; height: 10px;">&nbsp;</div> </div>
<h:outputText value="#{i18n['placeSelect.legend.green']}" /> </ui:fragment>
<div </ui:define>
style="border-color: black; border-style: solid; border-width: 1px; background-color: rgb(67, 66, 193); width: 10px; height: 10px;">&nbsp;</div> </ui:composition>
<h:outputText value="#{i18n['placeSelect.legend.blue']}" />
</h:panelGrid>
<h:panelGrid columnClasses=",rightalign" columns="2">
<h:outputLabel value="#{i18n['placeSelect.totalPlaces']}:" />
<h:outputText value="#{ajaxMapView.availablePlaces}" />
<h:outputLabel value="#{i18n['placeSelect.placesleft']}:" />
<h:outputText value="#{ajaxMapView.placesLeftToSelect}" />
</h:panelGrid>
</h:panelGrid>
<ui:fragment rendered="#{ajaxMapView.reserving}">
<div style="margin: 5px;">
<h:form id="placeselectformBottom">
<p:commandButton rendered="#{ajaxMapView.canUserBuy()}"
value="#{i18n['mapView.buyPlaces']}"
action="#{ajaxMapView.buySelectedPlaces()}" ajax="false" />
</h:form>
</div>
</ui:fragment>
</ui:define>
</ui:composition>
</h:body> </h:body>
</html> </html>
\ No newline at end of file
...@@ -57,14 +57,19 @@ function placemap(opts) ...@@ -57,14 +57,19 @@ function placemap(opts)
cannot_reserve: "Ei voitu varata paikkaa", cannot_reserve: "Ei voitu varata paikkaa",
cannot_release: "Ei voitu vapauttaa paikkaa", cannot_release: "Ei voitu vapauttaa paikkaa",
success_reserve: "Varattu paikka", success_reserve: "Varattu paikka",
success_release: "Vapautettu paikka" success_release: "Vapautettu paikka",
leavemessage: "Et ole lukinnut paikkojasi, oletko varma että haluat poistua?"
}; };
px.locales['en'] = { px.locales['en'] = {
cannot_reserve: "Can't reserve place", cannot_reserve: "Can't reserve place",
cannot_release: "Can't release place", cannot_release: "Can't release place",
success_reserve: "Place reserved", success_reserve: "Place reserved",
success_release: "Place released" success_release: "Place released",
leavemessage: "Forgot to lock your places, are you sure you want to leave?"
}; };
px.is_leave_message_on = false;
px.selected_count = 0;
var guide_layer = px.element.append("g").attr("id", "guides"); var guide_layer = px.element.append("g").attr("id", "guides");
...@@ -239,6 +244,8 @@ function placemap(opts) ...@@ -239,6 +244,8 @@ function placemap(opts)
//px.onclick(data); //px.onclick(data);
px.toggle_place(data); px.toggle_place(data);
//px.update_place(data.id); //px.update_place(data.id);
...@@ -257,6 +264,25 @@ function placemap(opts) ...@@ -257,6 +264,25 @@ function placemap(opts)
}*/ }*/
} }
px.update_leavemessage = function() {
if(px.is_leave_message_on) {
if(px.selected_count <= 0) {
px.selected_count = 0;
px.is_leave_message_on = false
$(window).unbind('beforeunload');
}
} else {
if(px.selected_count > 0) {
$(window).bind('beforeunload', function () {
return px.locales[px.locale].leavemessage;
});
px.is_leave_message_on = true;
}
}
}
function place_color(d, i) function place_color(d, i)
{ {
if (Object.keys(px.place_colors).indexOf(d.state) != -1) return px.place_colors[d.state]; if (Object.keys(px.place_colors).indexOf(d.state) != -1) return px.place_colors[d.state];
...@@ -285,6 +311,7 @@ function placemap(opts) ...@@ -285,6 +311,7 @@ function placemap(opts)
.style('stroke', '#101010') .style('stroke', '#101010')
.style('stroke-width', '1') .style('stroke-width', '1')
.append("title", function(d, i) { return d.name; }); .append("title", function(d, i) { return d.name; });
places places
.attr("x", function(d, i) { return d.x; }) .attr("x", function(d, i) { return d.x; })
.attr("y", function(d, i) { return d.y; }) .attr("y", function(d, i) { return d.y; })
...@@ -395,25 +422,28 @@ function placemap(opts) ...@@ -395,25 +422,28 @@ function placemap(opts)
console.log(data.places[0]); console.log(data.places[0]);
if (data.places[0].state == place.state) if (data.places[0].state == place.state)
{ {
if ( place.state == "F") PF("g").show([{"summary":px.locales[px.locale].cannot_reserve, if ( place.state == "F") PF("messages_growl").show([{"summary":px.locales[px.locale].cannot_reserve,
"detail":"", "detail":"",
"severity":"warn"}]); "severity":"warn"}]);
if ( place.state == "T") PF("g").show([{"summary":px.locales[px.locale].cannot_release, if ( place.state == "T") PF("messages_growl").show([{"summary":px.locales[px.locale].cannot_release,
"detail":"", "detail":"",
"severity":"warn"}]); "severity":"warn"}]);
} }
else if (data.places[0].state == "T") else if (data.places[0].state == "T")
{ {
PF("g").show([{"summary":px.locales[px.locale].success_reserve, // tän korjaus sotki ehkä jotain, otetaan poies
"detail":"", //PF("messages_growl").show([{"summary":px.locales[px.locale].success_reserve, "detail":"", "severity":"info"}]);
"severity":"info"}]); px.selected_count++;
} }
else if (data.places[0].state == "F") else if (data.places[0].state == "F") {
{ //PF("messages_growl").show([{"summary":px.locales[px.locale].success_release, "detail":"", "severity":"info"}]);
PF("g").show([{"summary":px.locales[px.locale].success_release, if (px.selected_count > 0) {
"detail":"", px.selected_count--;
"severity":"info"}]); } else {
px.selected_count = 0;
}
} }
px.update_leavemessage();
} }
); );
} }
......
...@@ -188,7 +188,7 @@ ...@@ -188,7 +188,7 @@
<p:menubar rendered="#{primeMenuView.hasSecondaryMenu}" model="#{primeMenuView.secondaryMenuModel}" /> <p:menubar rendered="#{primeMenuView.hasSecondaryMenu}" model="#{primeMenuView.secondaryMenuModel}" />
<h:form id="messages"> <h:form id="messages">
<p:growl id="growl" showDetail="true" /> <p:growl id="growl" showDetail="true" widgetVar="messages_growl" />
</h:form> </h:form>
......
...@@ -211,16 +211,17 @@ public class AjaxMapView extends GenericCDIView { ...@@ -211,16 +211,17 @@ public class AjaxMapView extends GenericCDIView {
return userview.getSelectedUser(); return userview.getSelectedUser();
} }
public String buySelectedPlaces() { public void buySelectedPlaces() {
try { try {
EventUser user = userview.getSelectedUser(); EventUser user = userview.getSelectedUser();
placebean.buySelectedPlaces(user); placebean.buySelectedPlaces(user);
quebean.remove(initMap(), user); quebean.remove(initMap(), user);
return "/place/myGroups"; // return "/place/myGroups";
super.navihandler.forward("/place/myGroups?faces-redirect=true");
} catch (BortalCatchableException e) { } catch (BortalCatchableException e) {
addFaceMessage("mapView.errorWhileBuyingPlaces"); addFaceMessage("mapView.errorWhileBuyingPlaces");
} }
return null;
} }
public boolean isMgmtPermission() public boolean isMgmtPermission()
......
...@@ -469,3 +469,4 @@ usercart.showoverview = Vie tarkastusn\u00E4kym\u00E4\u00E4n ...@@ -469,3 +469,4 @@ usercart.showoverview = Vie tarkastusn\u00E4kym\u00E4\u00E4n
viewlectures.title = Kurssit ja luennot viewlectures.title = Kurssit ja luennot
yes = Kyll\u00E4 yes = Kyll\u00E4
placeslot.state.expired=Vanhentunut
...@@ -1677,3 +1677,4 @@ voting.create.voteEnd = Voting close ...@@ -1677,3 +1677,4 @@ voting.create.voteEnd = Voting close
voting.create.voteStart = Voting start voting.create.voteStart = Voting start
yes = Yes yes = Yes
placeslot.state.expired=Expired
...@@ -1659,3 +1659,4 @@ voting.create.voteEnd = \u00C4\u00E4nestys kiinni ...@@ -1659,3 +1659,4 @@ voting.create.voteEnd = \u00C4\u00E4nestys kiinni
voting.create.voteStart = \u00C4\u00E4nestys auki voting.create.voteStart = \u00C4\u00E4nestys auki
yes = Kyll\u00E4 yes = Kyll\u00E4
placeslot.state.expired=Vanhentunut
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!