* Belegungsanzeiger

Zeigt die Belegung von Betten, Zellen und Schulen der eigenen und Verbandsgebäuden an.

  1. // ==UserScript==
  2. // @name * Belegungsanzeiger
  3. // @namespace bos-ernie.leitstellenspiel.de
  4. // @version 1.6.0
  5. // @license BSD-3-Clause
  6. // @author BOS-Ernie
  7. // @description Zeigt die Belegung von Betten, Zellen und Schulen der eigenen und Verbandsgebäuden an.
  8. // @match https://www.leitstellenspiel.de/
  9. // @match https://polizei.leitstellenspiel.de/
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=leitstellenspiel.de
  11. // @run-at document-idle
  12. // @grant none
  13. // @resource https://forum.leitstellenspiel.de/index.php?thread/23822-script-belegungsanzeiger-by-bos-ernie/
  14. // ==/UserScript==
  15.  
  16. /**
  17. * - Belegungsmarker integrieren
  18. * - Betten (eigene)
  19. * - Betten (Verband)
  20. * - Zellen (eigene)
  21. * - Zellen (Verband)
  22. */
  23.  
  24. (function () {
  25. let buildings;
  26. let allianceBuildings;
  27.  
  28. function addMenuEntry() {
  29. const divider = document.createElement("li");
  30. divider.setAttribute("class", "divider");
  31. divider.setAttribute("role", "presentation");
  32.  
  33. document.getElementById("logout_button").parentElement.parentElement.append(divider);
  34.  
  35. const bedIcon = document.createElement("span");
  36. bedIcon.setAttribute("class", "glyphicon glyphicon-blackboard");
  37.  
  38. const button = document.createElement("a");
  39. button.setAttribute("href", "javascript: void(0)");
  40. button.setAttribute("id", "occupancy-button");
  41. button.append(bedIcon);
  42. button.append(" Belegungsanzeiger");
  43. button.addEventListener("click", buttonClick);
  44.  
  45. const li = document.createElement("li");
  46. li.appendChild(button);
  47.  
  48. document.getElementById("logout_button").parentElement.parentElement.append(li);
  49. }
  50.  
  51. function addModal() {
  52. const modal = document.createElement("div");
  53. modal.className = "modal fade";
  54. modal.id = "occupancy-modal";
  55. modal.setAttribute("tabindex", "-1");
  56. modal.setAttribute("role", "dialog");
  57. modal.setAttribute("aria-labelledby", "occupancy-modal-label");
  58. modal.setAttribute("aria-hidden", "true");
  59. modal.style.display = "none";
  60. modal.style.zIndex = "5000";
  61. modal.innerHTML = `
  62. <div class="modal-dialog modal-lg" role="document" style="width: 1280px;">
  63. <div class="modal-content">
  64. <div class="modal-header">
  65. <h1 class="modal-title" id="occupancy-modal-label"><span class="glyphicon glyphicon-blackboard" aria-hidden="true"></span> Belegungsanzeiger</h1>
  66. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  67. <span aria-hidden="true">&times;</span>
  68. </button>
  69. </div>
  70. <div class="modal-body" style="max-height: calc(100vh - 212px);overflow-y: auto;">
  71. <div>
  72. <!-- Summary -->
  73. <table class="table table-striped table-hover">
  74. <thead>
  75. <tr>
  76. <th>Gebäudetyp</th>
  77. <th>Eigene Gebäude</th>
  78. <th>Verbandsgebäude</th>
  79. </tr>
  80. </thead>
  81. <tbody>
  82. <tr>
  83. <td>Krankenhäuser</td>
  84. <td id="summary-hospitals-occupancy">
  85. <div class="row">
  86. <div class="col-md-12">
  87. <div class="loader">
  88. <span></span>
  89. <span></span>
  90. <span></span>
  91. <span></span>
  92. </div>
  93. </div>
  94. </div>
  95. </td>
  96. <td id="summary-alliance-hospitals-occupancy">
  97. <div class="row">
  98. <div class="col-md-12">
  99. <div class="loader">
  100. <span></span>
  101. <span></span>
  102. <span></span>
  103. <span></span>
  104. </div>
  105. </div>
  106. </div>
  107. </td>
  108. </tr>
  109. <tr>
  110. <td>Zellen</td>
  111. <td id="summary-cells-occupancy">
  112. <div class="row">
  113. <div class="col-md-12">
  114. <div class="loader">
  115. <span></span>
  116. <span></span>
  117. <span></span>
  118. <span></span>
  119. </div>
  120. </div>
  121. </div>
  122. </td>
  123. <td id="summary-alliance-cells-occupancy">
  124. <div class="row">
  125. <div class="col-md-12">
  126. <div class="loader">
  127. <span></span>
  128. <span></span>
  129. <span></span>
  130. <span></span>
  131. </div>
  132. </div>
  133. </div>
  134. </td>
  135. </tr>
  136. <tr>
  137. <td>Feuerwehrschulen</td>
  138. <td id="summary-fire-academies-occupancy">
  139. <div class="row">
  140. <div class="col-md-12">
  141. <div class="loader">
  142. <span></span>
  143. <span></span>
  144. <span></span>
  145. <span></span>
  146. </div>
  147. </div>
  148. </div>
  149. </td>
  150. <td id="summary-alliance-fire-academies-occupancy">
  151. <div class="row">
  152. <div class="col-md-12">
  153. <div class="loader">
  154. <span></span>
  155. <span></span>
  156. <span></span>
  157. <span></span>
  158. </div>
  159. </div>
  160. </div>
  161. </td>
  162. </tr>
  163. <tr>
  164. <td>Rettungsschulen</td>
  165. <td id="summary-rescue-academies-occupancy">
  166. <div class="row">
  167. <div class="col-md-12">
  168. <div class="loader">
  169. <span></span>
  170. <span></span>
  171. <span></span>
  172. <span></span>
  173. </div>
  174. </div>
  175. </div>
  176. </td>
  177. <td id="summary-alliance-rescue-academies-occupancy">
  178. <div class="row">
  179. <div class="col-md-12">
  180. <div class="loader">
  181. <span></span>
  182. <span></span>
  183. <span></span>
  184. <span></span>
  185. </div>
  186. </div>
  187. </div>
  188. </td>
  189. </tr>
  190. <tr>
  191. <td>Polizeischulen</td>
  192. <td id="summary-police-academies-occupancy">
  193. <div class="row">
  194. <div class="col-md-12">
  195. <div class="loader">
  196. <span></span>
  197. <span></span>
  198. <span></span>
  199. <span></span>
  200. </div>
  201. </div>
  202. </div>
  203. </td>
  204. <td id="summary-alliance-police-academies-occupancy">
  205. <div class="row">
  206. <div class="col-md-12">
  207. <div class="loader">
  208. <span></span>
  209. <span></span>
  210. <span></span>
  211. <span></span>
  212. </div>
  213. </div>
  214. </div>
  215. </td>
  216. </tr>
  217. <tr>
  218. <tr>
  219. <td>Schule für Seefahrt und Seenotrettung</td>
  220. <td id="summary-search-and-rescue-academies-occupancy">
  221. <div class="row">
  222. <div class="col-md-12">
  223. <div class="loader">
  224. <span></span>
  225. <span></span>
  226. <span></span>
  227. <span></span>
  228. </div>
  229. </div>
  230. </div>
  231. </td>
  232. <td id="summary-alliance-search-and-rescue-academies-occupancy">
  233. <div class="row">
  234. <div class="col-md-12">
  235. <div class="loader">
  236. <span></span>
  237. <span></span>
  238. <span></span>
  239. <span></span>
  240. </div>
  241. </div>
  242. </div>
  243. </td>
  244. </tr>
  245. <tr>
  246. <td>THW Bundesschulen</td>
  247. <td id="summary-technical-aid-academies-occupancy">
  248. <div class="row">
  249. <div class="col-md-12">
  250. <div class="loader">
  251. <span></span>
  252. <span></span>
  253. <span></span>
  254. <span></span>
  255. </div>
  256. </div>
  257. </div>
  258. </td>
  259. <td id="summary-alliance-technical-aid-academies-occupancy">
  260. <div class="row">
  261. <div class="col-md-12">
  262. <div class="loader">
  263. <span></span>
  264. <span></span>
  265. <span></span>
  266. <span></span>
  267. </div>
  268. </div>
  269. </div>
  270. </td>
  271. </tr>
  272. </tbody>
  273. </table>
  274. <hr class="divider">
  275. <!-- Nav tabs -->
  276. <ul class="nav nav-tabs" role="tablist">
  277. <li role="presentation" class="active">
  278. <a href="#hospitals-panel" aria-controls="hospitals-panel" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-bed" aria-hidden="true"></span> Krankenhäuser</a>
  279. </li>
  280. <li role="presentation">
  281. <a href="#alliance-hospitals-panel" aria-controls="alliance-hospitals-panel" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-bed" aria-hidden="true"></span> Verbands-Krankenhäuser</a>
  282. </li>
  283. <li role="presentation">
  284. <a href="#cells-panel" aria-controls="cells-panel" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Zellen</a>
  285. </li>
  286. <li role="presentation">
  287. <a href="#alliance-cells-panel" aria-controls="alliance-cells-panel" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Verbandszellen</a>
  288. </li>
  289. <li role="presentation">
  290. <a href="#schools-panel" aria-controls="schools-panel" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-blackboard" aria-hidden="true"></span> Schulen</a>
  291. </li>
  292. <li role="presentation">
  293. <a href="#alliance-schools-panel" aria-controls="alliance-schools-panel" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-blackboard" aria-hidden="true"></span> Verbandsschulen</a>
  294. </li>
  295. </ul>
  296. <!-- Tab panes -->
  297. <div class="tab-content">
  298. <div role="tabpanel" class="tab-pane active" id="hospitals-panel">
  299. <div id="hospitals-occupancy">
  300. <div class="row">
  301. <div class="col-md-12 bg">
  302. <div class="loader">
  303. <span></span>
  304. <span></span>
  305. <span></span>
  306. <span></span>
  307. </div>
  308. </div>
  309. </div>
  310. </div>
  311. </div>
  312. <div role="tabpanel" class="tab-pane" id="alliance-hospitals-panel">
  313. <div id="alliance-hospitals-occupancy">
  314. <div class="row">
  315. <div class="col-md-12 bg">
  316. <div class="loader">
  317. <span></span>
  318. <span></span>
  319. <span></span>
  320. <span></span>
  321. </div>
  322. </div>
  323. </div>
  324. </div>
  325. </div>
  326. <div role="tabpanel" class="tab-pane" id="cells-panel">
  327. <div class="row">
  328. <div class="col-md-12 bg">
  329. <div class="loader">
  330. <span></span>
  331. <span></span>
  332. <span></span>
  333. <span></span>
  334. </div>
  335. </div>
  336. </div>
  337. </div>
  338. <div role="tabpanel" class="tab-pane" id="alliance-cells-panel">
  339. <div class="row">
  340. <div class="col-md-12 bg">
  341. <div class="loader">
  342. <span></span>
  343. <span></span>
  344. <span></span>
  345. <span></span>
  346. </div>
  347. </div>
  348. </div>
  349. </div>
  350. <div role="tabpanel" class="tab-pane" id="schools-panel">
  351. <div class="row">
  352. <div class="col-md-12 bg">
  353. <div class="loader">
  354. <span></span>
  355. <span></span>
  356. <span></span>
  357. <span></span>
  358. </div>
  359. </div>
  360. </div>
  361. </div>
  362. <div role="tabpanel" class="tab-pane" id="alliance-schools-panel">
  363. <div class="row">
  364. <div class="col-md-12 bg">
  365. <div class="loader">
  366. <span></span>
  367. <span></span>
  368. <span></span>
  369. <span></span>
  370. </div>
  371. </div>
  372. </div>
  373. </div>
  374. </div>
  375. </div>
  376. </div>
  377. </div>
  378. </div>
  379. `;
  380. document.body.appendChild(modal);
  381. }
  382.  
  383. function addStyle() {
  384. const style =
  385. ".loader{width:100px;height:100px;border-radius:100%;position:relative;margin:0 auto;top:40px;left:-2.5px}.loader span{display:inline-block;width:5px;height:20px;background-color:#c9302c}.loader span:first-child{animation:1s ease-in-out infinite grow}.loader span:nth-child(2){animation:1s ease-in-out .15s infinite grow}.loader span:nth-child(3){animation:1s ease-in-out .3s infinite grow}.loader span:nth-child(4){animation:1s ease-in-out .45s infinite grow}@keyframes grow{0%,100%{-webkit-transform:scaleY(1);-ms-transform:scaleY(1);-o-transform:scaleY(1);transform:scaleY(1)}50%{-webkit-transform:scaleY(1.8);-ms-transform:scaleY(1.8);-o-transform:scaleY(1.8);transform:scaleY(1.8)}}";
  386.  
  387. const styleElement = document.createElement("style");
  388. styleElement.innerHTML = style;
  389. document.head.appendChild(styleElement);
  390. }
  391.  
  392. async function fetchBuildings() {
  393. if (buildings) {
  394. return buildings;
  395. }
  396.  
  397. buildings = await fetch("/api/buildings")
  398. .then(response => response.json())
  399. .then(buildings => {
  400. return buildings.sort((a, b) => {
  401. if (a.caption < b.caption) return -1;
  402. if (a.caption > b.caption) return 1;
  403. return 0;
  404. });
  405. });
  406.  
  407. return buildings;
  408. }
  409.  
  410. async function fetchAllianceBuildings() {
  411. if (allianceBuildings) {
  412. return allianceBuildings;
  413. }
  414.  
  415. allianceBuildings = await fetch("/api/alliance_buildings")
  416. .then(response => response.json())
  417. .then(buildings => {
  418. return buildings.sort((a, b) => {
  419. if (a.caption < b.caption) return -1;
  420. if (a.caption > b.caption) return 1;
  421. return 0;
  422. });
  423. });
  424.  
  425. return allianceBuildings;
  426. }
  427.  
  428. function renderProgressbarAbsolute(capacity, used) {
  429. return renderProgressbar(capacity, used, used + "/" + capacity);
  430. }
  431.  
  432. function renderProgressbarRelative(capacity, used) {
  433. let occupancy = 0;
  434. if (capacity > 0) {
  435. occupancy = (used / capacity) * 100;
  436. }
  437.  
  438. return renderProgressbar(capacity, used, Math.round(occupancy) + "%");
  439. }
  440.  
  441. function renderProgressbar(capacity, used, innerText) {
  442. const occupancy = (used / capacity) * 100;
  443.  
  444. let progressBarColor = "progress-bar-success";
  445. if (occupancy >= 80) {
  446. progressBarColor = "progress-bar-danger";
  447. } else if (occupancy >= 40) {
  448. progressBarColor = "progress-bar-warning";
  449. }
  450.  
  451. const progressBarInner = document.createElement("div");
  452. progressBarInner.classList.add("progress-bar");
  453. progressBarInner.classList.add(progressBarColor);
  454. progressBarInner.setAttribute("role", "progressbar");
  455. progressBarInner.setAttribute("aria-valuenow", used);
  456. progressBarInner.setAttribute("aria-valuemin", "0");
  457. progressBarInner.setAttribute("aria-valuemax", capacity);
  458. progressBarInner.setAttribute("style", "width: " + occupancy + "%;");
  459. progressBarInner.innerText = innerText;
  460.  
  461. const progressBar = document.createElement("div");
  462. progressBar.classList.add("progress");
  463.  
  464. progressBar.appendChild(progressBarInner);
  465.  
  466. return progressBar;
  467. }
  468.  
  469. function renderHospitalsTable(hospitals) {
  470. const hospitalTable = document.createElement("table");
  471. hospitalTable.classList.add("table", "table-striped", "table-hover", "table-condensed");
  472.  
  473. const hospitalTableHeader = document.createElement("thead");
  474. const hospitalTableHeaderRow = document.createElement("tr");
  475. const hospitalTableHeaderCaption = document.createElement("th");
  476. hospitalTableHeaderCaption.innerText = "Krankenhaus";
  477. const hospitalTableHeaderLevel = document.createElement("th");
  478. hospitalTableHeaderLevel.innerText = "Kapazität";
  479. const hospitalTableHeaderAllianceShare = document.createElement("th");
  480. hospitalTableHeaderAllianceShare.innerText = "Provision";
  481. const hospitalTableHeaderOccupancy = document.createElement("th");
  482. hospitalTableHeaderOccupancy.innerText = "Auslastung";
  483.  
  484. hospitalTableHeaderRow.appendChild(hospitalTableHeaderCaption);
  485. hospitalTableHeaderRow.appendChild(hospitalTableHeaderLevel);
  486. hospitalTableHeaderRow.appendChild(hospitalTableHeaderAllianceShare);
  487. hospitalTableHeaderRow.appendChild(hospitalTableHeaderOccupancy);
  488. hospitalTableHeader.appendChild(hospitalTableHeaderRow);
  489.  
  490. const hospitalTableBody = document.createElement("tbody");
  491. hospitals.forEach(hospital => {
  492. const capacity = hospital.level + 10;
  493.  
  494. let levelColor = "warning";
  495. if (capacity === 30) {
  496. levelColor = "success";
  497. }
  498.  
  499. const capacitySpan = document.createElement("span");
  500. capacitySpan.classList.add("label", "label-" + levelColor);
  501. capacitySpan.innerText = capacity;
  502.  
  503. let creditsText = "Nicht geteilt";
  504. let creditsClass = "warning";
  505. if (hospital.hasOwnProperty("alliance_share_credits_percentage")) {
  506. if (hospital.alliance_share_credits_percentage === 10) {
  507. creditsClass = "success";
  508. }
  509. creditsText = hospital.alliance_share_credits_percentage + "%";
  510. } else {
  511. creditsClass = "danger";
  512. }
  513.  
  514. const creditsSpan = document.createElement("span");
  515. creditsSpan.classList.add("label", "label-" + creditsClass);
  516. creditsSpan.innerText = creditsText;
  517.  
  518. const buildingLink = document.createElement("a");
  519. buildingLink.href = "/buildings/" + hospital.id;
  520. buildingLink.innerText = hospital.caption;
  521. buildingLink.setAttribute("target", "_blank");
  522.  
  523. const hospitalTableRow = document.createElement("tr");
  524. const hospitalTableRowCaption = document.createElement("td");
  525. hospitalTableRowCaption.appendChild(buildingLink);
  526. const hospitalTableRowLevel = document.createElement("td");
  527. hospitalTableRowLevel.appendChild(capacitySpan);
  528. const hospitalTableRowAllianceShare = document.createElement("td");
  529. hospitalTableRowAllianceShare.appendChild(creditsSpan);
  530. const hospitalTableRowOccupancy = document.createElement("td");
  531. hospitalTableRowOccupancy.appendChild(renderProgressbarAbsolute(hospital.level + 10, hospital.patient_count));
  532.  
  533. hospitalTableRow.appendChild(hospitalTableRowCaption);
  534. hospitalTableRow.appendChild(hospitalTableRowLevel);
  535. hospitalTableRow.appendChild(hospitalTableRowAllianceShare);
  536. hospitalTableRow.appendChild(hospitalTableRowOccupancy);
  537. hospitalTableBody.appendChild(hospitalTableRow);
  538. });
  539.  
  540. hospitalTable.appendChild(hospitalTableHeader);
  541. hospitalTable.appendChild(hospitalTableBody);
  542.  
  543. return hospitalTable;
  544. }
  545.  
  546. function calculateNumberOfHospitalsAndTotalCapacity(hospitals) {
  547. let totalCapacity = 0;
  548.  
  549. hospitals.forEach(hospital => {
  550. totalCapacity += hospital.level + 10;
  551. });
  552.  
  553. return totalCapacity;
  554. }
  555.  
  556. async function renderHospitals() {
  557. await fetchBuildings()
  558. .then(buildings => buildings.filter(building => building.building_type === 4))
  559. .then(hospitals => {
  560. const hospitalTable = renderHospitalsTable(hospitals);
  561.  
  562. const totalCapacity = calculateNumberOfHospitalsAndTotalCapacity(hospitals);
  563.  
  564. const totalPatients = hospitals.reduce((total, hospital) => {
  565. return total + hospital.patient_count;
  566. }, 0);
  567.  
  568. const freeCapacity = totalCapacity - totalPatients;
  569.  
  570. const infoParagraph = document.createElement("p");
  571. infoParagraph.innerText =
  572. freeCapacity.toLocaleString() + " von " + totalCapacity.toLocaleString() + " Betten sind frei";
  573.  
  574. const summaryHospitalsOccupancyDiv = document.getElementById("summary-hospitals-occupancy");
  575. summaryHospitalsOccupancyDiv.innerHTML = "";
  576. summaryHospitalsOccupancyDiv.appendChild(renderProgressbarRelative(totalCapacity, totalPatients));
  577. summaryHospitalsOccupancyDiv.appendChild(infoParagraph);
  578.  
  579. const hospitalsOccupancyDiv = document.getElementById("hospitals-occupancy");
  580. hospitalsOccupancyDiv.innerHTML = "";
  581. hospitalsOccupancyDiv.appendChild(hospitalTable);
  582. });
  583. }
  584.  
  585. async function renderAllianceHospitals() {
  586. await fetchAllianceBuildings()
  587. .then(buildings => buildings.filter(building => building.building_type === 4))
  588. .then(hospitals => {
  589. const hospitalTable = renderHospitalsTable(hospitals);
  590.  
  591. const totalCapacity = calculateNumberOfHospitalsAndTotalCapacity(hospitals);
  592.  
  593. const totalPatients = hospitals.reduce((total, hospital) => {
  594. return total + hospital.patient_count;
  595. }, 0);
  596.  
  597. const freeCapacity = totalCapacity - totalPatients;
  598.  
  599. const infoParagraph = document.createElement("p");
  600. infoParagraph.innerText =
  601. freeCapacity.toLocaleString() + " von " + totalCapacity.toLocaleString() + " Betten sind frei";
  602.  
  603. const summaryHospitalsOccupancyDiv = document.getElementById("summary-alliance-hospitals-occupancy");
  604. summaryHospitalsOccupancyDiv.innerHTML = "";
  605. summaryHospitalsOccupancyDiv.appendChild(renderProgressbarRelative(totalCapacity, totalPatients));
  606. summaryHospitalsOccupancyDiv.appendChild(infoParagraph);
  607.  
  608. const hospitalsOccupancyDiv = document.getElementById("alliance-hospitals-occupancy");
  609. hospitalsOccupancyDiv.innerHTML = "";
  610. hospitalsOccupancyDiv.appendChild(hospitalTable);
  611. });
  612. }
  613.  
  614. function renderCellsTable(buildings) {
  615. const cellsTable = document.createElement("table");
  616. cellsTable.classList.add("table", "table-striped");
  617.  
  618. const cellsTableHeaderRow = document.createElement("tr");
  619. const cellsTableHeaderCaption = document.createElement("th");
  620. cellsTableHeaderCaption.innerText = "Name";
  621. const cellsTableHeaderCapacity = document.createElement("th");
  622. cellsTableHeaderCapacity.innerText = "Kapazität";
  623. const cellsTableHeaderProvision = document.createElement("th");
  624. cellsTableHeaderProvision.innerText = "Provision";
  625. const cellsTableHeaderOccupancy = document.createElement("th");
  626. cellsTableHeaderOccupancy.innerText = "Auslastung";
  627.  
  628. const cellsTableHeader = document.createElement("thead");
  629. cellsTableHeaderRow.appendChild(cellsTableHeaderCaption);
  630. cellsTableHeaderRow.appendChild(cellsTableHeaderCapacity);
  631. cellsTableHeaderRow.appendChild(cellsTableHeaderProvision);
  632. cellsTableHeaderRow.appendChild(cellsTableHeaderOccupancy);
  633. cellsTableHeader.appendChild(cellsTableHeaderRow);
  634.  
  635. const cellsTableBody = document.createElement("tbody");
  636. buildings.forEach(building => {
  637. let capacityClass = "warning";
  638.  
  639. if (building.smallBuilding === false && building.numberOfCells === 10) {
  640. capacityClass = "success";
  641. }
  642. if (building.smallBuilding === true && building.numberOfCells === 2) {
  643. capacityClass = "success";
  644. }
  645.  
  646. const capacitySpan = document.createElement("span");
  647. capacitySpan.classList.add("label", "label-" + capacityClass);
  648. capacitySpan.innerText = building.numberOfCells;
  649.  
  650. const buildingLink = document.createElement("a");
  651. buildingLink.href = "/buildings/" + building.id;
  652. buildingLink.innerText = building.caption;
  653. buildingLink.setAttribute("target", "_blank");
  654.  
  655. let creditsText = "Nicht geteilt";
  656. let provisionClass = "warning";
  657. if (building.provision !== undefined) {
  658. if (building.provision === 10) {
  659. provisionClass = "success";
  660. }
  661.  
  662. creditsText = building.provision + "%";
  663. } else {
  664. provisionClass = "danger";
  665. }
  666.  
  667. const provisionSpan = document.createElement("span");
  668. provisionSpan.classList.add("label", "label-" + provisionClass);
  669. provisionSpan.innerText = creditsText;
  670.  
  671. const cellsTableRowCaption = document.createElement("td");
  672. cellsTableRowCaption.appendChild(buildingLink);
  673. const cellsTableRowCapacity = document.createElement("td");
  674. cellsTableRowCapacity.appendChild(capacitySpan);
  675. const cellsTableRowProvision = document.createElement("td");
  676. cellsTableRowProvision.appendChild(provisionSpan);
  677. const cellsTableRowOccupancy = document.createElement("td");
  678. cellsTableRowOccupancy.append(renderProgressbarAbsolute(building.numberOfCells, building.numberOfPrisoners));
  679.  
  680. const cellsTableRow = document.createElement("tr");
  681. cellsTableRow.appendChild(cellsTableRowCaption);
  682. cellsTableRow.appendChild(cellsTableRowCapacity);
  683. cellsTableRow.appendChild(cellsTableRowProvision);
  684. cellsTableRow.appendChild(cellsTableRowOccupancy);
  685. cellsTableBody.appendChild(cellsTableRow);
  686. });
  687.  
  688. cellsTable.appendChild(cellsTableHeader);
  689. cellsTable.appendChild(cellsTableBody);
  690.  
  691. return cellsTable;
  692. }
  693.  
  694. async function renderCells() {
  695. // policeStations constitutes an array of objects which have the following structure:
  696. // {
  697. // id: 123456,
  698. // caption: "Polizeistation",
  699. // buildingType: 6, // Either 6 or 19
  700. // numberOfCells: 10, // sum of all extensions of type id 0 through 9
  701. // numberOfPrisoners: 5, // taken from prisoner_count of the building
  702. // }
  703.  
  704. let policeStations = await fetchBuildings()
  705. .then(buildings => buildings.filter(building => building.building_type === 6 || building.building_type === 19))
  706. .then(policeStations => {
  707. return policeStations.map(policeStation => {
  708. let numberOfCells = policeStation.extensions.filter(
  709. extension => extension.type_id >= 0 && extension.type_id <= 9,
  710. ).length;
  711.  
  712. return {
  713. id: policeStation.id,
  714. caption: policeStation.caption,
  715. buildingType: policeStation.building_type,
  716. smallBuilding: policeStation.small_building,
  717. numberOfCells: numberOfCells,
  718. numberOfPrisoners: policeStation.prisoner_count,
  719. provision: policeStation.alliance_share_credits_percentage,
  720. };
  721. });
  722. });
  723.  
  724. const totalCapacity = policeStations.reduce(
  725. (accumulator, currentValue) => accumulator + currentValue.numberOfCells,
  726. 0,
  727. );
  728. const totalOccupancy = policeStations.reduce(
  729. (accumulator, currentValue) => accumulator + currentValue.numberOfPrisoners,
  730. 0,
  731. );
  732.  
  733. const freeCapacity = totalCapacity - totalOccupancy;
  734.  
  735. const infoParagraph = document.createElement("p");
  736. infoParagraph.innerText =
  737. freeCapacity.toLocaleString() + " von " + totalCapacity.toLocaleString() + " Zellen sind frei";
  738.  
  739. const summaryCellsOccupancyDiv = document.getElementById("summary-cells-occupancy");
  740. summaryCellsOccupancyDiv.innerHTML = "";
  741. summaryCellsOccupancyDiv.appendChild(renderProgressbarRelative(totalCapacity, totalOccupancy));
  742. summaryCellsOccupancyDiv.appendChild(infoParagraph);
  743.  
  744. const cellsPanel = document.getElementById("cells-panel");
  745. cellsPanel.innerHTML = "";
  746. cellsPanel.appendChild(renderCellsTable(policeStations));
  747. }
  748.  
  749. function renderAllianceCellsTable(policeStations) {
  750. const cellsTable = document.createElement("table");
  751. cellsTable.classList.add("table", "table-bordered", "table-striped");
  752.  
  753. const cellsTableHeaderRow = document.createElement("tr");
  754. const cellsTableHeaderCaption = document.createElement("th");
  755. cellsTableHeaderCaption.innerText = "Gebäude";
  756. const cellsTableHeaderCapacity = document.createElement("th");
  757. cellsTableHeaderCapacity.innerText = "Kapazität";
  758. const cellsTableHeaderProvision = document.createElement("th");
  759. cellsTableHeaderProvision.innerText = "Provision";
  760. const cellsTableHeaderOccupancy = document.createElement("th");
  761. cellsTableHeaderOccupancy.innerText = "Auslastung";
  762.  
  763. const cellsTableHeader = document.createElement("thead");
  764. cellsTableHeaderRow.appendChild(cellsTableHeaderCaption);
  765. cellsTableHeaderRow.appendChild(cellsTableHeaderCapacity);
  766. cellsTableHeaderRow.appendChild(cellsTableHeaderProvision);
  767. cellsTableHeaderRow.appendChild(cellsTableHeaderOccupancy);
  768. cellsTableHeader.appendChild(cellsTableHeaderRow);
  769.  
  770. const cellsTableBody = document.createElement("tbody");
  771. policeStations.forEach(building => {
  772. let capacityClass = "warning";
  773.  
  774. if (building.smallBuilding === false && building.numberOfCells === 10) {
  775. capacityClass = "success";
  776. }
  777. if (building.smallBuilding === true && building.numberOfCells === 2) {
  778. capacityClass = "success";
  779. }
  780.  
  781. const capacitySpan = document.createElement("span");
  782. capacitySpan.classList.add("label", "label-" + capacityClass);
  783. capacitySpan.innerText = building.numberOfCells;
  784.  
  785. const buildingLink = document.createElement("a");
  786. buildingLink.href = "/buildings/" + building.id;
  787. buildingLink.innerText = building.caption;
  788. buildingLink.setAttribute("target", "_blank");
  789.  
  790. let provisionClass = "warning";
  791. if (building.provision === 0) {
  792. provisionClass = "success";
  793. }
  794.  
  795. const provisionSpan = document.createElement("span");
  796. provisionSpan.classList.add("label", "label-" + provisionClass);
  797. provisionSpan.innerText = building.provision;
  798. if (!isNaN(building.provision)) {
  799. provisionSpan.innerText += "%";
  800. }
  801.  
  802. const cellsTableRowCaption = document.createElement("td");
  803. cellsTableRowCaption.appendChild(buildingLink);
  804. const cellsTableRowCapacity = document.createElement("td");
  805. cellsTableRowCapacity.appendChild(capacitySpan);
  806. const cellsTableRowProvision = document.createElement("td");
  807. cellsTableRowProvision.appendChild(provisionSpan);
  808. const cellsTableRowOccupancy = document.createElement("td");
  809. cellsTableRowOccupancy.appendChild(renderProgressbarAbsolute(building.numberOfCells, building.numberOfPrisoners));
  810.  
  811. const cellsTableRow = document.createElement("tr");
  812. cellsTableRow.appendChild(cellsTableRowCaption);
  813. cellsTableRow.appendChild(cellsTableRowCapacity);
  814. cellsTableRow.appendChild(cellsTableRowProvision);
  815. cellsTableRow.appendChild(cellsTableRowOccupancy);
  816.  
  817. cellsTableBody.appendChild(cellsTableRow);
  818. });
  819.  
  820. cellsTable.appendChild(cellsTableHeader);
  821. cellsTable.appendChild(cellsTableBody);
  822.  
  823. return cellsTable;
  824. }
  825.  
  826. async function renderAllianceCells() {
  827. let alliancePoliceStations = await fetchAllianceBuildings()
  828. .then(buildings => buildings.filter(building => building.building_type === 16))
  829. .then(policeStations => {
  830. return policeStations.map(policeStation => {
  831. let numberOfCells = policeStation.extensions.filter(
  832. extension => extension.type_id >= 0 && extension.type_id <= 9,
  833. ).length;
  834.  
  835. return {
  836. id: policeStation.id,
  837. caption: policeStation.caption,
  838. buildingType: policeStation.building_type,
  839. smallBuilding: policeStation.small_building,
  840. numberOfCells: numberOfCells,
  841. numberOfPrisoners: policeStation.prisoner_count,
  842. provision: policeStation.alliance_share_credits_percentage,
  843. };
  844. });
  845. });
  846.  
  847. const totalCapacity = alliancePoliceStations.reduce(
  848. (accumulator, currentValue) => accumulator + currentValue.numberOfCells,
  849. 0,
  850. );
  851. const totalOccupancy = alliancePoliceStations.reduce(
  852. (accumulator, currentValue) => accumulator + currentValue.numberOfPrisoners,
  853. 0,
  854. );
  855.  
  856. const freeCapacity = totalCapacity - totalOccupancy;
  857.  
  858. const infoParagraph = document.createElement("p");
  859. infoParagraph.innerText =
  860. freeCapacity.toLocaleString() + " von " + totalCapacity.toLocaleString() + " Zellen sind frei";
  861.  
  862. const summaryCellsOccupancyDiv = document.getElementById("summary-alliance-cells-occupancy");
  863. summaryCellsOccupancyDiv.innerHTML = "";
  864. summaryCellsOccupancyDiv.appendChild(renderProgressbarRelative(totalCapacity, totalOccupancy));
  865. summaryCellsOccupancyDiv.appendChild(infoParagraph);
  866.  
  867. const cellsPanel = document.getElementById("alliance-cells-panel");
  868. cellsPanel.innerHTML = "";
  869. cellsPanel.appendChild(renderAllianceCellsTable(alliancePoliceStations));
  870. }
  871.  
  872. function renderSchoolsTable(schools, name) {
  873. const schoolsTable = document.createElement("table");
  874. schoolsTable.classList.add("table", "table-bordered", "table-striped");
  875.  
  876. const schoolsTableHeaderRow = document.createElement("tr");
  877. const schoolsTableHeaderCaption = document.createElement("th");
  878. schoolsTableHeaderCaption.innerText = name;
  879. const schoolsTableHeaderCapacity = document.createElement("th");
  880. schoolsTableHeaderCapacity.innerText = "Kapazität";
  881. const schoolsTableHeaderOccupancy = document.createElement("th");
  882. schoolsTableHeaderOccupancy.innerText = "Auslastung";
  883.  
  884. const schoolsTableHeader = document.createElement("thead");
  885. schoolsTableHeaderRow.appendChild(schoolsTableHeaderCaption);
  886. schoolsTableHeaderRow.appendChild(schoolsTableHeaderCapacity);
  887. schoolsTableHeaderRow.appendChild(schoolsTableHeaderOccupancy);
  888. schoolsTableHeader.appendChild(schoolsTableHeaderRow);
  889.  
  890. const schoolsTableBody = document.createElement("tbody");
  891. schools.forEach(building => {
  892. const buildingLink = document.createElement("a");
  893. buildingLink.href = "/buildings/" + building.id;
  894. buildingLink.innerText = building.caption;
  895. buildingLink.setAttribute("target", "_blank");
  896.  
  897. let capacityClass = "warning";
  898. if (building.capacity === 4) {
  899. capacityClass = "success";
  900. }
  901.  
  902. let capacitySpan = document.createElement("span");
  903. capacitySpan.classList.add("label", "label-" + capacityClass);
  904. capacitySpan.innerText = building.capacity;
  905.  
  906. const schoolsTableRowCaption = document.createElement("td");
  907. schoolsTableRowCaption.appendChild(buildingLink);
  908. const schoolsTableRowCapacity = document.createElement("td");
  909. schoolsTableRowCapacity.appendChild(capacitySpan);
  910. const schoolsTableRowOccupancy = document.createElement("td");
  911. schoolsTableRowOccupancy.appendChild(renderProgressbarAbsolute(building.capacity, building.occupancy));
  912.  
  913. const schoolsTableRow = document.createElement("tr");
  914. schoolsTableRow.appendChild(schoolsTableRowCaption);
  915. schoolsTableRow.appendChild(schoolsTableRowCapacity);
  916. schoolsTableRow.appendChild(schoolsTableRowOccupancy);
  917.  
  918. schoolsTableBody.appendChild(schoolsTableRow);
  919. });
  920.  
  921. schoolsTable.appendChild(schoolsTableHeader);
  922. schoolsTable.appendChild(schoolsTableBody);
  923.  
  924. return schoolsTable;
  925. }
  926.  
  927. async function getSchoolsMeta(buildingTypeId) {
  928. return await fetchBuildings()
  929. .then(buildings => buildings.filter(building => building.building_type === buildingTypeId))
  930. .then(schools => {
  931. return schools.map(school => {
  932. let numberOfRooms = school.extensions.filter(
  933. extension => extension.type_id >= 0 && extension.type_id <= 2,
  934. ).length;
  935.  
  936. return {
  937. id: school.id,
  938. caption: school.caption,
  939. buildingType: school.building_type,
  940. capacity: 1 + numberOfRooms,
  941. occupancy: school.schoolings.length,
  942. provision: school.alliance_share_credits_percentage,
  943. };
  944. });
  945. });
  946. }
  947.  
  948. async function renderSchools() {
  949. const schoolTypes = [
  950. { id: 1, name: "Feuerwehrschulen", divId: "summary-fire-academies-occupancy" },
  951. { id: 3, name: "Rettungsschulen", divId: "summary-rescue-academies-occupancy" },
  952. { id: 8, name: "Polizeischulen", divId: "summary-police-academies-occupancy" },
  953. { id: 10, name: "THW Bundesschulen", divId: "summary-technical-aid-academies-occupancy" },
  954. { id: 27, name: "Schule für Seefahrt und Seenotrettung", divId: "summary-search-and-rescue-academies-occupancy" },
  955. ];
  956.  
  957. const schoolsPanel = document.getElementById("schools-panel");
  958. schoolsPanel.innerHTML = "";
  959.  
  960. for (const schoolType of schoolTypes) {
  961. let schools = await getSchoolsMeta(schoolType.id);
  962.  
  963. const totalCapacity = schools.reduce((accumulator, currentValue) => accumulator + currentValue.capacity, 0);
  964. const totalOccupancy = schools.reduce((accumulator, currentValue) => accumulator + currentValue.occupancy, 0);
  965.  
  966. const freeCapacity = totalCapacity - totalOccupancy;
  967.  
  968. const infoParagraph = document.createElement("p");
  969. infoParagraph.innerText =
  970. freeCapacity.toLocaleString() + " von " + totalCapacity.toLocaleString() + " Klassenräumen sind frei";
  971.  
  972. const summaryCellsOccupancyDiv = document.getElementById(schoolType.divId);
  973. summaryCellsOccupancyDiv.innerHTML = "";
  974. summaryCellsOccupancyDiv.appendChild(renderProgressbarRelative(totalCapacity, totalOccupancy));
  975. summaryCellsOccupancyDiv.appendChild(infoParagraph);
  976.  
  977. schoolsPanel.appendChild(renderSchoolsTable(schools, schoolType.name));
  978. }
  979. }
  980.  
  981. async function getAllianceSchoolsMeta(buildingTypeId) {
  982. return await fetchAllianceBuildings()
  983. .then(buildings => buildings.filter(building => building.building_type === buildingTypeId))
  984. .then(schools => {
  985. return schools.map(school => {
  986. let numberOfRooms = school.extensions.filter(
  987. extension => extension.type_id >= 0 && extension.type_id <= 2,
  988. ).length;
  989.  
  990. return {
  991. id: school.id,
  992. caption: school.caption,
  993. buildingType: school.building_type,
  994. capacity: 1 + numberOfRooms,
  995. // Return school.schoolings.length as occupancy
  996. // if school.schoolings is undefined, apply 0
  997. occupancy: school.schoolings ? school.schoolings.length : 0,
  998. provision: school.alliance_share_credits_percentage,
  999. };
  1000. });
  1001. });
  1002. }
  1003.  
  1004. async function renderAllianceSchools() {
  1005. const schoolTypes = [
  1006. { id: 1, name: "Feuerwehrschulen", divId: "summary-alliance-fire-academies-occupancy" },
  1007. { id: 3, name: "Rettungsschulen", divId: "summary-alliance-rescue-academies-occupancy" },
  1008. { id: 8, name: "Polizeischulen", divId: "summary-alliance-police-academies-occupancy" },
  1009. { id: 10, name: "THW Bundesschulen", divId: "summary-alliance-technical-aid-academies-occupancy" },
  1010. {
  1011. id: 27,
  1012. name: "Schule für Seefahrt und Seenotrettung",
  1013. divId: "summary-alliance-search-and-rescue-academies-occupancy",
  1014. },
  1015. ];
  1016.  
  1017. const schoolsPanel = document.getElementById("alliance-schools-panel");
  1018. schoolsPanel.innerHTML = "";
  1019.  
  1020. for (const schoolType of schoolTypes) {
  1021. let allianceSchools = await getAllianceSchoolsMeta(schoolType.id);
  1022.  
  1023. const totalCapacity = allianceSchools.reduce(
  1024. (accumulator, currentValue) => accumulator + currentValue.capacity,
  1025. 0,
  1026. );
  1027. const totalOccupancy = allianceSchools.reduce(
  1028. (accumulator, currentValue) => accumulator + currentValue.occupancy,
  1029. 0,
  1030. );
  1031.  
  1032. const freeCapacity = totalCapacity - totalOccupancy;
  1033.  
  1034. const infoParagraph = document.createElement("p");
  1035. infoParagraph.innerText =
  1036. freeCapacity.toLocaleString() + " von " + totalCapacity.toLocaleString() + " Klassenräumen sind frei";
  1037.  
  1038. const summarySchoolsOccupancyDiv = document.getElementById(schoolType.divId);
  1039. summarySchoolsOccupancyDiv.innerHTML = "";
  1040. summarySchoolsOccupancyDiv.appendChild(renderProgressbarRelative(totalCapacity, totalOccupancy));
  1041. summarySchoolsOccupancyDiv.appendChild(infoParagraph);
  1042.  
  1043. schoolsPanel.appendChild(renderSchoolsTable(allianceSchools, schoolType.name));
  1044. }
  1045. }
  1046.  
  1047. function buttonClick(event) {
  1048. event.preventDefault();
  1049.  
  1050. $("#occupancy-modal").modal("show");
  1051.  
  1052. renderHospitals();
  1053. renderAllianceHospitals();
  1054. renderCells();
  1055. renderAllianceCells();
  1056. renderSchools();
  1057. renderAllianceSchools();
  1058. }
  1059.  
  1060. function main() {
  1061. addStyle();
  1062. addModal();
  1063. addMenuEntry();
  1064. }
  1065.  
  1066. main();
  1067. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址