demo.html 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <title>Planning Report – MVP Demo</title>
  7. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
  8. <script defer src="https://unpkg.com/html2pdf.js@0.10.1/dist/html2pdf.bundle.min.js"></script>
  9. <style>
  10. body { background: #0f172a0d; }
  11. header { background: #0f172a; color: #fff; }
  12. .report-card { background:#fff; border-radius:12px; box-shadow: 0 10px 30px rgba(0,0,0,.05); }
  13. .mono { font-family: ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace; }
  14. textarea { tab-size: 2; }
  15. </style>
  16. </head>
  17. <body>
  18. <header class="py-4 mb-4">
  19. <div class="container">
  20. <h1 class="h3 mb-0">Planning Report Generator — MVP</h1>
  21. <div class="text-white-50">Standalone demo UI posting to the PHP endpoint</div>
  22. </div>
  23. </header>
  24. <main class="container pb-5">
  25. <div class="row g-4">
  26. <div class="col-lg-6">
  27. <div class="card h-100">
  28. <div class="card-header d-flex align-items-center justify-content-between">
  29. <strong>Payload</strong>
  30. <div class="d-flex gap-2">
  31. <button id="btnSample" class="btn btn-outline-secondary btn-sm">Load sample payload</button>
  32. <button id="btnPretty" class="btn btn-outline-secondary btn-sm">Pretty JSON</button>
  33. </div>
  34. </div>
  35. <div class="card-body">
  36. <textarea id="payload" class="form-control mono" rows="24" spellcheck="false" placeholder='Paste your JSON payload here...'></textarea>
  37. <div class="mt-3 d-flex gap-2">
  38. <button id="btnGenerate" class="btn btn-primary">Generate report</button>
  39. <div id="status" class="small text-muted"></div>
  40. </div>
  41. <div class="form-text mt-2">Endpoint: <code id="endpointTxt"></code></div>
  42. </div>
  43. </div>
  44. </div>
  45. <div class="col-lg-6">
  46. <div class="card report-card">
  47. <div class="card-header d-flex justify-content-between align-items-center">
  48. <strong>Report preview</strong>
  49. <div class="d-flex gap-2">
  50. <button id="btnCopyMd" class="btn btn-outline-secondary btn-sm" disabled>Copy Markdown</button>
  51. <button id="btnSavePdf" class="btn btn-outline-secondary btn-sm" disabled>Save as PDF</button>
  52. </div>
  53. </div>
  54. <div class="card-body" id="reportWrap">
  55. <div id="reportHtml" class="mb-3"></div>
  56. <details id="mdBlock" class="mt-3" open>
  57. <summary class="mb-2">Markdown</summary>
  58. <textarea id="reportMd" class="form-control mono" rows="16" readonly></textarea>
  59. </details>
  60. </div>
  61. </div>
  62. </div>
  63. </div>
  64. </main>
  65. <script>
  66. const REPORT_ENDPOINT = "generate_planning_report.php";
  67. document.getElementById('endpointTxt').textContent = REPORT_ENDPOINT;
  68. function loadSample() {
  69. const sample = {
  70. address: "24 Clifton Drive, Sorell TAS 7172",
  71. lat: -42.781, lng: 147.560,
  72. pid: "1234567",
  73. title_id: "CT 12345/1",
  74. total_area: { sqm_label: "1,652 m²", ha_label: "0.1652 ha" },
  75. area_sqm: "1652", area_ha: "0.1652",
  76. tenure: "Freehold",
  77. lpi: "LIST LPI XYZ",
  78. list_guid: "abcde-12345-fghi-67890",
  79. locality: "Sorell",
  80. council: "Sorell Council",
  81. planning_scheme: "Tasmanian Planning Scheme",
  82. planning_zones: ["General Residential Zone"],
  83. planning_codes: ["Parking and Sustainable Transport", "Road and Railway Assets", "Signs"],
  84. use_class: "Health Services",
  85. proposal_summary: "The proposal seeks approval for the use and development of an allied-health clinic within an existing building. Works include internal reconfiguration, accessible entry upgrade, and compliant parking layout.",
  86. operations: { hours: "7:30am–6:00pm Mon–Fri", staff: 8, children: 0 },
  87. parking: { cars: 12, bikes: 4, accessible: 1, motorcycle: 0 },
  88. signage: [{ type: "Wall Sign", desc: "Non-illuminated, 1.5 m² near entry." }],
  89. consultants: { TIA: "Midson Traffic (2025)", Acoustic: "DDEG (2025)" },
  90. overlays: { bushfire: false, airport_noise: false, road_rail_attenuation: true },
  91. standards: [
  92. { clause: "8.3.1", standard: "Discretionary uses", acceptable: "A3", relies_on_pc: ["P1","P4"], notes: "" },
  93. { clause: "8.5.1", standard: "Non-residential development", acceptable: "A1,A2,A6", relies_on_pc: [], notes: "Complies as proposed." },
  94. { clause: "C2.5.1", standard: "Car parking numbers", acceptable: "A1", relies_on_pc: [], notes: "" },
  95. { clause: "C2.6.1", standard: "Bicycle parking", acceptable: "A1", relies_on_pc: [], notes: "" },
  96. { clause: "C3.5.1", standard: "Road and railway assets", acceptable: "", relies_on_pc: ["P1"], notes: "TIA supports performance solution." }
  97. ],
  98. appendices: ["Application Form","Certificate of Title","Architectural Plans","Traffic Impact Assessment","Acoustic Report"],
  99. map_png: "",
  100. prepared_for: "Client / Applicant Name",
  101. prepared_by: "Modulos Design",
  102. author: "Benjamin Harris",
  103. job_number: "PRJ-001",
  104. version: "Draft",
  105. prepared_date: "5 September 2025"
  106. };
  107. document.getElementById('payload').value = JSON.stringify(sample, null, 2);
  108. }
  109. function pretty() {
  110. const ta = document.getElementById('payload');
  111. try {
  112. const obj = JSON.parse(ta.value);
  113. ta.value = JSON.stringify(obj, null, 2);
  114. } catch {}
  115. }
  116. async function generate() {
  117. const ta = document.getElementById('payload');
  118. const status = document.getElementById('status');
  119. const btnMd = document.getElementById('btnCopyMd');
  120. const btnPdf = document.getElementById('btnSavePdf');
  121. let payload;
  122. btnMd.disabled = true; btnPdf.disabled = true;
  123. try { payload = JSON.parse(ta.value); }
  124. catch (e) { alert("Invalid JSON in payload."); return; }
  125. status.textContent = "Generating…";
  126. try {
  127. const resp = await fetch("generate_planning_report.php", {
  128. method: "POST",
  129. headers: { "Content-Type": "application/json", "Accept": "application/json" },
  130. body: JSON.stringify(payload)
  131. });
  132. const raw = await resp.text(); // <-- read raw
  133. let out = null;
  134. try { out = JSON.parse(raw); } catch (_) {}
  135. if (!resp.ok || !out || out.ok !== true) {
  136. // Show a helpful snippet of what we actually got back
  137. const snippet = (raw || '').slice(0, 300).replace(/\s+/g, ' ').trim();
  138. throw new Error(out && out.error ? out.error : `HTTP ${resp.status} – non-JSON or error body: ${snippet || "<empty>"}`);
  139. }
  140. document.getElementById('reportHtml').innerHTML = out.html || "<p>No HTML returned.</p>";
  141. const md = out.markdown || "";
  142. const mdTa = document.getElementById('reportMd');
  143. mdTa.value = md;
  144. btnMd.disabled = !md;
  145. btnPdf.disabled = !(out.html && out.html.length);
  146. status.textContent = "Done.";
  147. } catch (e) {
  148. console.error(e);
  149. status.textContent = "Error.";
  150. alert("Report generation error: " + e.message);
  151. }
  152. }
  153. async function copyMd() {
  154. const mdTa = document.getElementById('reportMd');
  155. try {
  156. await navigator.clipboard.writeText(mdTa.value);
  157. const btn = document.getElementById('btnCopyMd');
  158. btn.textContent = "Copied!";
  159. setTimeout(()=>btn.textContent="Copy Markdown", 1200);
  160. } catch {
  161. mdTa.select(); document.execCommand('copy');
  162. }
  163. }
  164. function savePdf() {
  165. const container = document.querySelector('#reportHtml');
  166. if (!container || !container.innerHTML.trim()) { alert("Generate a report first."); return; }
  167. const opts = {
  168. margin: [10,10,10,10],
  169. filename: "Planning Report.pdf",
  170. image: { type: "jpeg", quality: 0.95 },
  171. html2canvas: { scale: 2, useCORS: true, logging: false },
  172. jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }
  173. };
  174. html2pdf().from(container).set(opts).save();
  175. }
  176. document.getElementById('btnSample').addEventListener('click', loadSample);
  177. document.getElementById('btnPretty').addEventListener('click', pretty);
  178. document.getElementById('btnGenerate').addEventListener('click', generate);
  179. document.getElementById('btnCopyMd').addEventListener('click', copyMd);
  180. document.getElementById('btnSavePdf').addEventListener('click', savePdf);
  181. // Preload sample for convenience
  182. loadSample();
  183. </script>
  184. </body>
  185. </html>