1import { 2 PROBLEM_REPORT_CATEGORIES, 3 PROBLEM_SEVERITIES, 4 ProblemReportStateService 5} from "./chunk-AFCLXHHF.js"; 6import { 7 Network 8} from "./chunk-53QL3RYY.js"; 9import { 10 ApiService 11} from "./chunk-UN7A4OF7.js"; 12import { 13 LocationService 14} from "./chunk-K52XM66R.js"; 15import "./chunk-PYYASMRY.js"; 16import "./chunk-63DLEHZF.js"; 17import "./chunk-PFRFKH6U.js"; 18import "./chunk-UZKEIBGO.js"; 19import "./chunk-2E4THSOE.js"; 20import { 21 GPSService 22} from "./chunk-JISY7VJD.js"; 23import "./chunk-KP4RHBP7.js"; 24import "./chunk-QI7JV4VS.js"; 25import "./chunk-OTQ6GTAJ.js"; 26import { 27 IonBackButton, 28 IonButton, 29 IonButtons, 30 IonCard, 31 IonCardContent, 32 IonContent, 33 IonHeader, 34 IonSelect, 35 IonSelectOption, 36 IonSpinner, 37 IonTextarea, 38 IonTitle, 39 IonToolbar 40} from "./chunk-RLZ522BW.js"; 41import { 42 DatabaseService 43} from "./chunk-RLJCY5HS.js"; 44import { 45 ToastService 46} from "./chunk-GR5DEE5Y.js"; 47import { 48 TranslateModule, 49 TranslatePipe, 50 TranslateService 51} from "./chunk-O5XYYQCR.js"; 52import "./chunk-QGEJYSW7.js"; 53import { 54 StorageService 55} from "./chunk-MAIAEXGG.js"; 56import "./chunk-L4O6SE3A.js"; 57import "./chunk-INK5THDW.js"; 58import { 59 NgIcon, 60 lucideAccessibility, 61 lucideBuilding, 62 lucideCamera, 63 lucideCircleHelp, 64 lucideDroplets, 65 lucideImage, 66 lucideMapPin, 67 lucideSend, 68 lucideSparkles, 69 lucideTriangleAlert, 70 lucideWifi, 71 lucideWifiOff, 72 lucideX, 73 lucideZap, 74 provideIcons 75} from "./chunk-GMPIA5L4.js"; 76import { 77 BehaviorSubject, 78 Component, 79 EventEmitter, 80 FormsModule, 81 Injectable, 82 Input, 83 MaxLengthValidator, 84 NgControlStatus, 85 NgControlStatusGroup, 86 NgForm, 87 NgModel, 88 Output, 89 Router, 90 Subject, 91 catchError, 92 concatMap, 93 delay,
94 firstValueFrom, 95 from, 96 of, 97 setClassMetadata, 98 takeUntil, 99 tap, 100 ɵNgNoValidate, 101 ɵsetClassDebugInfo, 102 ɵɵProvidersFeature, 103 ɵɵadvance, 104 ɵɵclassProp, 105 ɵɵconditional, 106 ɵɵdefineComponent, 107 ɵɵdefineInjectable, 108 ɵɵdirectiveInject, 109 ɵɵelement, 110 ɵɵelementEnd, 111 ɵɵelementStart, 112 ɵɵgetCurrentView, 113 ɵɵinject, 114 ɵɵlistener, 115 ɵɵnextContext, 116 ɵɵpipe, 117 ɵɵpipeBind1, 118 ɵɵpipeBind2, 119 ɵɵproperty, 120 ɵɵpureFunction1, 121 ɵɵrepeater, 122 ɵɵrepeaterCreate, 123 ɵɵresetView, 124 ɵɵrestoreView, 125 ɵɵsanitizeUrl, 126 ɵɵtemplate, 127 ɵɵtext, 128 ɵɵtextInterpolate, 129 ɵɵtextInterpolate1, 130 ɵɵtwoWayBindingSet, 131 ɵɵtwoWayListener, 132 ɵɵtwoWayProperty 133} from "./chunk-UQ6LLOJ6.js"; 134import "./chunk-BD3NMR2S.js"; 135import "./chunk-6HPUB4U6.js"; 136import "./chunk-JTG2GCS6.js"; 137import "./chunk-WGFJL45C.js"; 138import "./chunk-7WVISTDL.js"; 139import "./chunk-2S57T3GS.js"; 140import "./chunk-R4QHACMO.js"; 141import "./chunk-4MEJEBWM.js"; 142import "./chunk-RIBVNXHQ.js"; 143import "./chunk-Z42OVMMO.js"; 144import "./chunk-O5S42IXR.js"; 145import "./chunk-QBR2RTFG.js"; 146import "./chunk-MIOLVGSM.js"; 147import "./chunk-V3YFOG6Q.js"; 148import "./chunk-LPMBLWVS.js"; 149import { 150 CapacitorException, 151 WebPlugin, 152 registerPlugin 153} from "./chunk-5LKHIIQE.js"; 154import "./chunk-ITE2OHLM.js"; 155import "./chunk-C46CB72A.js"; 156import "./chunk-SRPSML5O.js"; 157import "./chunk-NRSVX4KN.js"; 158import "./chunk-T4GYAOTA.js"; 159import "./chunk-2VHYZHLL.js"; 160import "./chunk-S6LHOZ5A.js"; 161import "./chunk-Y6HZOH3Q.js"; 162import "./chunk-TWQHADEW.js"; 163import "./chunk-3J4L7S6G.js"; 164import "./chunk-IL42VKT6.js"; 165import "./chunk-LONO5O5R.js"; 166import "./chunk-KFBF5L4X.js"; 167import "./chunk-PWC4K6CW.js"; 168import "./chunk-R6JLIESI.js"; 169import "./chunk-UG6K57PI.js"; 170import "./chunk-UR6VIRNL.js"; 171import "./chunk-OJO5RD3M.js"; 172import { 173 __async 174} from "./chunk-CO64IJQE.js"; 175
vendor: 2,941 bytes, lines 176-237
176// node_modules/@capacitor/camera/dist/esm/definitions.js 177var CameraSource; 178(function(CameraSource2) { 179 CameraSource2["Prompt"] = "PROMPT"; 180 CameraSource2["Camera"] = "CAMERA"; 181 CameraSource2["Photos"] = "PHOTOS"; 182})(CameraSource || (CameraSource = {})); 183var CameraDirection; 184(function(CameraDirection2) { 185 CameraDirection2["Rear"] = "REAR"; 186 CameraDirection2["Front"] = "FRONT"; 187})(CameraDirection || (CameraDirection = {})); 188var CameraResultType; 189(function(CameraResultType2) { 190 CameraResultType2["Uri"] = "uri"; 191 CameraResultType2["Base64"] = "base64"; 192 CameraResultType2["DataUrl"] = "dataUrl"; 193})(CameraResultType || (CameraResultType = {})); 194var MediaType; 195(function(MediaType2) { 196 MediaType2[MediaType2["Photo"] = 0] = "Photo"; 197 MediaType2[MediaType2["Video"] = 1] = "Video"; 198})(MediaType || (MediaType = {})); 199var MediaTypeSelection; 200(function(MediaTypeSelection2) { 201 MediaTypeSelection2[MediaTypeSelection2["Photo"] = 0] = "Photo"; 202 MediaTypeSelection2[MediaTypeSelection2["Video"] = 1] = "Video"; 203 MediaTypeSelection2[MediaTypeSelection2["All"] = 2] = "All"; 204})(MediaTypeSelection || (MediaTypeSelection = {})); 205var EncodingType; 206(function(EncodingType2) { 207 EncodingType2[EncodingType2["JPEG"] = 0] = "JPEG"; 208 EncodingType2[EncodingType2["PNG"] = 1] = "PNG"; 209})(EncodingType || (EncodingType = {})); 210var CameraErrorCode; 211(function(CameraErrorCode2) { 212 CameraErrorCode2["CameraPermissionDenied"] = "OS-PLUG-CAMR-0003"; 213 CameraErrorCode2["GalleryPermissionDenied"] = "OS-PLUG-CAMR-0005"; 214 CameraErrorCode2["NoCameraAvailable"] = "OS-PLUG-CAMR-0007"; 215 CameraErrorCode2["TakePhotoCancelled"] = "OS-PLUG-CAMR-0006"; 216 CameraErrorCode2["TakePhotoFailed"] = "OS-PLUG-CAMR-0010"; 217 CameraErrorCode2["TakePhotoInvalidArguments"] = "OS-PLUG-CAMR-0014"; 218 CameraErrorCode2["InvalidImageData"] = "OS-PLUG-CAMR-0008"; 219 CameraErrorCode2["EditPhotoFailed"] = "OS-PLUG-CAMR-0009"; 220 CameraErrorCode2["EditPhotoCancelled"] = "OS-PLUG-CAMR-0013"; 221 CameraErrorCode2["EditPhotoEmptyUri"] = "OS-PLUG-CAMR-0024"; 222 CameraErrorCode2["ImageNotFound"] = "OS-PLUG-CAMR-0011"; 223 CameraErrorCode2["ProcessImageFailed"] = "OS-PLUG-CAMR-0012"; 224 CameraErrorCode2["ChooseMediaFailed"] = "OS-PLUG-CAMR-0018"; 225 CameraErrorCode2["ChooseMediaCancelled"] = "OS-PLUG-CAMR-0020"; 226 CameraErrorCode2["MediaPathError"] = "OS-PLUG-CAMR-0021"; 227 CameraErrorCode2["FetchImageFromUriFailed"] = "OS-PLUG-CAMR-0028"; 228 CameraErrorCode2["RecordVideoFailed"] = "OS-PLUG-CAMR-0016"; 229 CameraErrorCode2["RecordVideoCancelled"] = "OS-PLUG-CAMR-0017"; 230 CameraErrorCode2["VideoNotFound"] = "OS-PLUG-CAMR-0025"; 231 CameraErrorCode2["PlayVideoFailed"] = "OS-PLUG-CAMR-0023"; 232 CameraErrorCode2["EncodeResultFailed"] = "OS-PLUG-CAMR-0019"; 233 CameraErrorCode2["FileNotFound"] = "OS-PLUG-CAMR-0027"; 234 CameraErrorCode2["InvalidArgument"] = "OS-PLUG-CAMR-0031"; 235 CameraErrorCode2["GeneralError"] = "OS-PLUG-CAMR-0026"; 236})(CameraErrorCode || (CameraErrorCode = {})); 237
vendor: 17,193 bytes, lines 238-746
238// node_modules/@capacitor/camera/dist/esm/web.js 239var CameraWeb = class extends WebPlugin { 240 takePhoto(options) { 241 return __async(this, null, function* () { 242 return new Promise((resolve, reject) => __async(this, null, function* () { 243 if (options.webUseInput) { 244 this.takePhotoCameraInputExperience(options, resolve, reject); 245 } else { 246 this.takePhotoCameraExperience(options, resolve, reject); 247 } 248 })); 249 }); 250 } 251 recordVideo(_options) { 252 return __async(this, null, function* () { 253 throw this.unimplemented("recordVideo is not implemented on Web."); 254 }); 255 } 256 playVideo(_options) { 257 return __async(this, null, function* () { 258 throw this.unimplemented("playVideo is not implemented on Web."); 259 }); 260 } 261 chooseFromGallery(options) { 262 return __async(this, null, function* () { 263 return new Promise((resolve, reject) => __async(this, null, function* () { 264 this.galleryInputExperience(options, resolve, reject); 265 })); 266 }); 267 } 268 editPhoto(_options) { 269 return __async(this, null, function* () { 270 throw this.unimplemented("editPhoto is not implemented on Web."); 271 }); 272 } 273 editURIPhoto(_options) { 274 return __async(this, null, function* () { 275 throw this.unimplemented("editURIPhoto is not implemented on Web."); 276 }); 277 } 278 getPhoto(options) { 279 return __async(this, null, function* () { 280 return new Promise((resolve, reject) => __async(this, null, function* () { 281 if (options.webUseInput || options.source === CameraSource.Photos) { 282 this.fileInputExperience(options, resolve, reject); 283 } else if (options.source === CameraSource.Prompt) { 284 let actionSheet = document.querySelector("pwa-action-sheet"); 285 if (!actionSheet) { 286 actionSheet = document.createElement("pwa-action-sheet"); 287 document.body.appendChild(actionSheet); 288 } 289 actionSheet.header = options.promptLabelHeader || "Photo"; 290 actionSheet.cancelable = false; 291 actionSheet.options = [{ 292 title: options.promptLabelPhoto || "From Photos" 293 }, { 294 title: options.promptLabelPicture || "Take Picture" 295 }]; 296 actionSheet.addEventListener("onSelection", (e) => __async(this, null, function* () { 297 const selection = e.detail; 298 if (selection === 0) { 299 this.fileInputExperience(options, resolve, reject); 300 } else { 301 this.cameraExperience(options, resolve, reject); 302 } 303 })); 304 } else { 305 this.cameraExperience(options, resolve, reject); 306 } 307 })); 308 }); 309 } 310 pickImages(_options) { 311 return __async(this, null, function* () { 312 return new Promise((resolve, reject) => __async(this, null, function* () { 313 this.multipleFileInputExperience(resolve, reject); 314 })); 315 }); 316 } 317 cameraExperience(options, resolve, reject) { 318 return __async(this, null, function* () { 319 yield this._setupPWACameraModal(options.direction, (photo) => this._getCameraPhoto(photo, options), () => this.fileInputExperience(options, resolve, reject), resolve, reject); 320 }); 321 } 322 fileInputExperience(options, resolve, reject) { 323 let input = document.querySelector("#_capacitor-camera-input"); 324 const cleanup = () => { 325 var _a; 326 (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); 327 }; 328 if (!input) { 329 input = document.createElement("input"); 330 input.id = "_capacitor-camera-input"; 331 input.type = "file"; 332 input.hidden = true; 333 document.body.appendChild(input); 334 input.addEventListener("change", (_e) => { 335 const file = input.files[0]; 336 let format = "jpeg"; 337 if (file.type === "image/png") { 338 format = "png"; 339 } else if (file.type === "image/gif") { 340 format = "gif"; 341 } 342 if (options.resultType === "dataUrl" || options.resultType === "base64") { 343 const reader = new FileReader(); 344 reader.addEventListener("load", () => { 345 if (options.resultType === "dataUrl") { 346 resolve({ 347 dataUrl: reader.result, 348 format 349 }); 350 } else if (options.resultType === "base64") { 351 const b64 = reader.result.split(",")[1]; 352 resolve({ 353 base64String: b64, 354 format 355 }); 356 } 357 cleanup(); 358 }); 359 reader.readAsDataURL(file); 360 } else { 361 resolve({ 362 webPath: URL.createObjectURL(file), 363 format 364 }); 365 cleanup(); 366 } 367 }); 368 input.addEventListener("cancel", (_e) => { 369 reject(new CapacitorException("User cancelled photos app")); 370 cleanup(); 371 }); 372 } 373 input.accept = "image/*"; 374 input.capture = true; 375 if (options.source === CameraSource.Photos || options.source === CameraSource.Prompt) { 376 input.removeAttribute("capture"); 377 } else if (options.direction === CameraDirection.Front) { 378 input.capture = "user"; 379 } else if (options.direction === CameraDirection.Rear) { 380 input.capture = "environment"; 381 } 382 input.click(); 383 } 384 multipleFileInputExperience(resolve, reject) { 385 let input = document.querySelector("#_capacitor-camera-input-multiple"); 386 const cleanup = () => { 387 var _a; 388 (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); 389 }; 390 if (!input) { 391 input = document.createElement("input"); 392 input.id = "_capacitor-camera-input-multiple"; 393 input.type = "file"; 394 input.hidden = true; 395 input.multiple = true; 396 document.body.appendChild(input); 397 input.addEventListener("change", (_e) => { 398 const photos = []; 399 for (let i = 0; i < input.files.length; i++) { 400 const file = input.files[i]; 401 let format = "jpeg"; 402 if (file.type === "image/png") { 403 format = "png"; 404 } else if (file.type === "image/gif") { 405 format = "gif"; 406 } 407 photos.push({ 408 webPath: URL.createObjectURL(file), 409 format 410 }); 411 } 412 resolve({ 413 photos 414 }); 415 cleanup(); 416 }); 417 input.addEventListener("cancel", (_e) => { 418 reject(new CapacitorException("User cancelled photos app")); 419 cleanup(); 420 }); 421 } 422 input.accept = "image/*"; 423 input.click(); 424 } 425 _getCameraPhoto(photo, options) { 426 return new Promise((resolve, reject) => { 427 const reader = new FileReader(); 428 const format = this._getFileFormat(photo); 429 if (options.resultType === "uri") { 430 resolve({ 431 webPath: URL.createObjectURL(photo), 432 format, 433 saved: false 434 }); 435 } else { 436 reader.readAsDataURL(photo); 437 reader.onloadend = () => { 438 const r = reader.result; 439 if (options.resultType === "dataUrl") { 440 resolve({ 441 dataUrl: r, 442 format, 443 saved: false 444 }); 445 } else { 446 resolve({ 447 base64String: r.split(",")[1], 448 format, 449 saved: false 450 }); 451 } 452 }; 453 reader.onerror = (e) => { 454 reject(e); 455 }; 456 } 457 }); 458 } 459 takePhotoCameraExperience(options, resolve, reject) { 460 return __async(this, null, function* () { 461 yield this._setupPWACameraModal(options.cameraDirection, (photo) => { 462 var _a; 463 return this._buildPhotoMediaResult(photo, (_a = options.includeMetadata) !== null && _a !== void 0 ? _a : false); 464 }, () => this.takePhotoCameraInputExperience(options, resolve, reject), resolve, reject); 465 }); 466 } 467 takePhotoCameraInputExperience(options, resolve, reject) { 468 const input = this._createFileInput("_capacitor-camera-input-takephoto"); 469 const cleanup = () => { 470 var _a; 471 (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); 472 }; 473 input.onchange = (_e) => __async(this, null, function* () { 474 var _a; 475 if (!this._validateFileInput(input, reject, cleanup)) { 476 return; 477 } 478 const file = input.files[0]; 479 resolve(yield this._buildPhotoMediaResult(file, (_a = options.includeMetadata) !== null && _a !== void 0 ? _a : false)); 480 cleanup(); 481 }); 482 input.oncancel = () => { 483 reject(new CapacitorException("User cancelled photos app")); 484 cleanup(); 485 }; 486 input.accept = "image/*"; 487 if (options.cameraDirection === CameraDirection.Front) { 488 input.capture = "user"; 489 } else { 490 input.capture = "environment"; 491 } 492 input.click(); 493 } 494 galleryInputExperience(options, resolve, reject) { 495 var _a, _b; 496 const input = this._createFileInput("_capacitor-camera-input-gallery"); 497 input.multiple = (_a = options.allowMultipleSelection) !== null && _a !== void 0 ? _a : false; 498 const cleanup = () => { 499 var _a2; 500 (_a2 = input.parentNode) === null || _a2 === void 0 ? void 0 : _a2.removeChild(input); 501 }; 502 input.onchange = (_e) => __async(this, null, function* () { 503 var _a2; 504 if (!this._validateFileInput(input, reject, cleanup)) { 505 return; 506 } 507 const results = []; 508 for (let i = 0; i < input.files.length; i++) { 509 const file = input.files[i]; 510 if (file.type.startsWith("image/")) { 511 results.push(yield this._buildPhotoMediaResult(file, (_a2 = options.includeMetadata) !== null && _a2 !== void 0 ? _a2 : false)); 512 } else if (file.type.startsWith("video/")) { 513 const format = this._getFileFormat(file); 514 let thumbnail; 515 let resolution; 516 let duration; 517 try { 518 const videoInfo = yield this._getVideoMetadata(file); 519 thumbnail = videoInfo.thumbnail; 520 if (options.includeMetadata) { 521 resolution = videoInfo.resolution; 522 duration = videoInfo.duration; 523 } 524 } catch (e) { 525 console.warn("Failed to get video metadata:", e); 526 } 527 const result = { 528 type: MediaType.Video, 529 thumbnail, 530 webPath: URL.createObjectURL(file), 531 saved: false 532 }; 533 if (options.includeMetadata) { 534 result.metadata = { 535 format, 536 resolution, 537 size: file.size, 538 creationDate: new Date(file.lastModified).toISOString(), 539 duration 540 }; 541 } 542 results.push(result); 543 } 544 } 545 resolve({ 546 results 547 }); 548 cleanup(); 549 }); 550 input.oncancel = () => { 551 reject(new CapacitorException("User cancelled photos app")); 552 cleanup(); 553 }; 554 const mediaType = (_b = options.mediaType) !== null && _b !== void 0 ? _b : MediaTypeSelection.Photo; 555 if (mediaType === MediaTypeSelection.Photo) { 556 input.accept = "image/*"; 557 } else if (mediaType === MediaTypeSelection.Video) { 558 input.accept = "video/*"; 559 } else { 560 input.accept = "image/*,video/*"; 561 } 562 input.click(); 563 } 564 _getFileFormat(file) { 565 if (file.type === "image/png") { 566 return "png"; 567 } else if (file.type === "image/gif") { 568 return "gif"; 569 } else if (file.type.startsWith("video/")) { 570 return file.type.split("/")[1]; 571 } else if (file.type.startsWith("image/")) { 572 return "jpeg"; 573 } 574 return file.type.split("/")[1] || "jpeg"; 575 } 576 _buildPhotoMediaResult(file, includeMetadata) { 577 return __async(this, null, function* () { 578 const format = this._getFileFormat(file); 579 const thumbnail = yield this._getBase64FromFile(file); 580 const result = { 581 type: MediaType.Photo, 582 thumbnail, 583 webPath: URL.createObjectURL(file), 584 saved: false 585 }; 586 if (includeMetadata) { 587 const resolution = yield this._getImageResolution(file); 588 result.metadata = { 589 format, 590 resolution, 591 size: file.size, 592 creationDate: "lastModified" in file ? new Date(file.lastModified).toISOString() : (/* @__PURE__ */ new Date()).toISOString() 593 }; 594 } 595 return result; 596 }); 597 } 598 _validateFileInput(input, reject, cleanup) { 599 if (!input.files || input.files.length === 0) { 600 const message = input.multiple ? "No files selected" : "No file selected"; 601 reject(new CapacitorException(message)); 602 cleanup(); 603 return false; 604 } 605 return true; 606 } 607 _setupPWACameraModal(cameraDirection, onPhotoCallback, fallbackCallback, resolve, reject) { 608 return __async(this, null, function* () { 609 if (customElements.get("pwa-camera-modal")) { 610 const cameraModal = document.createElement("pwa-camera-modal"); 611 cameraModal.facingMode = cameraDirection === CameraDirection.Front ? "user" : "environment"; 612 document.body.appendChild(cameraModal); 613 try { 614 yield cameraModal.componentOnReady(); 615 cameraModal.addEventListener("onPhoto", (e) => __async(this, null, function* () { 616 const photo = e.detail; 617 if (photo === null) { 618 reject(new CapacitorException("User cancelled photos app")); 619 } else if (photo instanceof Error) { 620 reject(photo); 621 } else { 622 resolve(yield onPhotoCallback(photo)); 623 } 624 cameraModal.dismiss(); 625 document.body.removeChild(cameraModal); 626 })); 627 cameraModal.present(); 628 } catch (e) { 629 fallbackCallback(); 630 } 631 } else { 632 console.error(`Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/web/pwa-elements.`); 633 fallbackCallback(); 634 } 635 }); 636 } 637 _createFileInput(id) { 638 let input = document.querySelector(`#${id}`); 639 if (!input) { 640 input = document.createElement("input"); 641 input.id = id; 642 input.type = "file"; 643 input.hidden = true; 644 document.body.appendChild(input); 645 } 646 return input; 647 } 648 _getImageResolution(image) { 649 return __async(this, null, function* () { 650 try { 651 const bitmap = yield createImageBitmap(image); 652 const resolution = `${bitmap.width}x${bitmap.height}`; 653 bitmap.close(); 654 return resolution; 655 } catch (e) { 656 console.warn("Failed to get image resolution:", e); 657 return void 0; 658 } 659 }); 660 } 661 _getBase64FromFile(file) { 662 return new Promise((resolve, reject) => { 663 const reader = new FileReader(); 664 reader.onloadend = () => { 665 const dataUrl = reader.result; 666 const base64 = dataUrl.split(",")[1]; 667 resolve(base64); 668 }; 669 reader.onerror = (e) => { 670 reject(e); 671 }; 672 reader.readAsDataURL(file); 673 }); 674 } 675 _getVideoMetadata(videoFile) { 676 return new Promise((resolve) => { 677 const video = document.createElement("video"); 678 video.preload = "metadata"; 679 video.muted = true; 680 video.onloadedmetadata = () => { 681 const seekTime = Math.min(1, video.duration * 0.1); 682 video.currentTime = seekTime; 683 }; 684 video.onseeked = () => { 685 const result = { 686 resolution: `${video.videoWidth}x${video.videoHeight}`, 687 duration: video.duration 688 }; 689 try { 690 const canvas = document.createElement("canvas"); 691 canvas.width = video.videoWidth; 692 canvas.height = video.videoHeight; 693 const ctx = canvas.getContext("2d"); 694 if (ctx) { 695 ctx.drawImage(video, 0, 0, canvas.width, canvas.height); 696 result.thumbnail = canvas.toDataURL("image/jpeg", 0.8).split(",")[1]; 697 } 698 } catch (e) { 699 console.warn("Failed to generate video thumbnail:", e); 700 } 701 URL.revokeObjectURL(video.src); 702 resolve(result); 703 }; 704 video.onerror = () => { 705 URL.revokeObjectURL(video.src); 706 resolve({}); 707 }; 708 video.src = URL.createObjectURL(videoFile); 709 }); 710 } 711 checkPermissions() { 712 return __async(this, null, function* () { 713 if (typeof navigator === "undefined" || !navigator.permissions) { 714 throw this.unavailable("Permissions API not available in this browser"); 715 } 716 try { 717 const permission = yield window.navigator.permissions.query({ 718 name: "camera" 719 }); 720 return { 721 camera: permission.state, 722 photos: "granted" 723 }; 724 } catch (_a) { 725 throw this.unavailable("Camera permissions are not available in this browser"); 726 } 727 }); 728 } 729 requestPermissions() { 730 return __async(this, null, function* () { 731 throw this.unimplemented("Not implemented on web."); 732 }); 733 } 734 pickLimitedLibraryPhotos() { 735 return __async(this, null, function* () { 736 throw this.unavailable("Not implemented on web."); 737 }); 738 } 739 getLimitedLibraryPhotos() { 740 return __async(this, null, function* () { 741 throw this.unavailable("Not implemented on web."); 742 }); 743 } 744}; 745var Camera = new CameraWeb(); 746
vendor: 127 bytes, lines 747-751
747// node_modules/@capacitor/camera/dist/esm/index.js 748var Camera2 = registerPlugin("Camera", { 749 web: () => new CameraWeb() 750}); 751
752// src/app/shared/components/photo-capture/photo-capture.component.ts 753function PhotoCaptureComponent_Conditional_2_Template(rf, ctx) { 754 if (rf & 1) { 755 const _r1 = \u0275\u0275getCurrentView(); 756 \u0275\u0275element(0, "img", 8); 757 \u0275\u0275elementStart(1, "button", 9); 758 \u0275\u0275listener("click", function PhotoCaptureComponent_Conditional_2_Template_button_click_1_listener() { 759 \u0275\u0275restoreView(_r1); 760 const ctx_r1 = \u0275\u0275nextContext(); 761 return \u0275\u0275resetView(ctx_r1.removePhoto()); 762 }); 763 \u0275\u0275element(2, "ng-icon", 10); 764 \u0275\u0275elementEnd(); 765 } 766 if (rf & 2) { 767 const ctx_r1 = \u0275\u0275nextContext(); 768 \u0275\u0275property("src", ctx_r1.currentPhoto, \u0275\u0275sanitizeUrl); 769 } 770} 771function PhotoCaptureComponent_Conditional_3_Template(rf, ctx) { 772 if (rf & 1) { 773 const _r3 = \u0275\u0275getCurrentView(); 774 \u0275\u0275elementStart(0, "div", 11); 775 \u0275\u0275listener("click", function PhotoCaptureComponent_Conditional_3_Template_div_click_0_listener() { 776 \u0275\u0275restoreView(_r3); 777 const ctx_r1 = \u0275\u0275nextContext(); 778 return \u0275\u0275resetView(ctx_r1.captureFromCamera()); 779 }); 780 \u0275\u0275element(1, "ng-icon", 12); 781 \u0275\u0275elementStart(2, "span"); 782 \u0275\u0275text(3); 783 \u0275\u0275pipe(4, "translate"); 784 \u0275\u0275elementEnd()(); 785 } 786 if (rf & 2) { 787 \u0275\u0275advance(3); 788 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(4, 1, "problemReport.photo.placeholder")); 789 } 790} 791function PhotoCaptureComponent_Conditional_4_Template(rf, ctx) { 792 if (rf & 1) { 793 \u0275\u0275elementStart(0, "div", 3); 794 \u0275\u0275element(1, "ion-spinner", 13); 795 \u0275\u0275elementEnd(); 796 } 797} 798var _PhotoCaptureComponent = class _PhotoCaptureComponent { 799 constructor() { 800 this.currentPhoto = null; 801 this.photoCapture = new EventEmitter(); 802 this.photoRemove = new EventEmitter(); 803 this.isCapturing = false; 804 } 805 captureFromCamera() { 806 return __async(this, null, function* () { 807 yield this.capturePhoto(CameraSource.Camera); 808 }); 809 } 810 captureFromGallery() { 811 return __async(this, null, function* () { 812 yield this.capturePhoto(CameraSource.Photos); 813 }); 814 } 815 capturePhoto(source) { 816 return __async(this, null, function* () { 817 if (this.isCapturing) 818 return; 819 this.isCapturing = true; 820 try { 821 const image = yield Camera2.getPhoto({ 822 quality: 80, 823 allowEditing: false, 824 resultType: CameraResultType.Base64, 825 source, 826 width: 1920, 827 height: 1080, 828 correctOrientation: true 829 }); 830 if (image.base64String) { 831 const mimeType = image.format === "png" ? "image/png" : "image/jpeg"; 832 const dataUrl = `data:${mimeType};base64,${image.base64String}`; 833 this.photoCapture.emit(dataUrl); 834 } 835 } catch (error) { 836 if ((error == null ? void 0 : error.message) !== "User cancelled photos app") { 837 console.error("Photo capture error:", error); 838 } 839 } finally { 840 this.isCapturing = false; 841 } 842 }); 843 } 844 removePhoto() { 845 this.photoRemove.emit(); 846 } 847}; 848_PhotoCaptureComponent.\u0275fac = function PhotoCaptureComponent_Factory(__ngFactoryType__) { 849 return new (__ngFactoryType__ || _PhotoCaptureComponent)(); 850}; 851_PhotoCaptureComponent.\u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _PhotoCaptureComponent, selectors: [["app-photo-capture"]], inputs: { currentPhoto: "currentPhoto" }, outputs: { photoCapture: "photoCapture", photoRemove: "photoRemove" }, features: [\u0275\u0275ProvidersFeature([], [provideIcons({ lucideCamera, lucideImage, lucideX })])], decls: 14, vars: 12, consts: [[1, "photo-capture-container"], [1, "photo-preview"], [1, "placeholder"], [1, "capturing-overlay"], [1, "capture-buttons"], ["fill", "outline", "size", "small", 3, "click", "disabled"], ["name", "lucideCamera", "slot", "start"], ["name", "lucideImage", "slot", "start"], ["alt", "Problem photo", 3, "src"], ["type", "button", 1, "remove-button", 3, "click"], ["name", "lucideX", "size", "16"], [1, "placeholder", 3, "click"], ["name", "lucideCamera", "size", "48"], ["name", "crescent"]], template: function PhotoCaptureComponent_Template(rf, ctx) { 852 if (rf & 1) { 853 \u0275\u0275elementStart(0, "div", 0)(1, "div", 1); 854 \u0275\u0275template(2, PhotoCaptureComponent_Conditional_2_Template, 3, 1)(3, PhotoCaptureComponent_Conditional_3_Template, 5, 3, "div", 2)(4, PhotoCaptureComponent_Conditional_4_Template, 2, 0, "div", 3); 855 \u0275\u0275elementEnd(); 856 \u0275\u0275elementStart(5, "div", 4)(6, "ion-button", 5); 857 \u0275\u0275listener("click", function PhotoCaptureComponent_Template_ion_button_click_6_listener() { 858 return ctx.captureFromCamera(); 859 }); 860 \u0275\u0275element(7, "ng-icon", 6); 861 \u0275\u0275text(8); 862 \u0275\u0275pipe(9, "translate"); 863 \u0275\u0275elementEnd(); 864 \u0275\u0275elementStart(10, "ion-button", 5); 865 \u0275\u0275listener("click", function PhotoCaptureComponent_Template_ion_button_click_10_listener() { 866 return ctx.captureFromGallery(); 867 }); 868 \u0275\u0275element(11, "ng-icon", 7); 869 \u0275\u0275text(12); 870 \u0275\u0275pipe(13, "translate"); 871 \u0275\u0275elementEnd()()(); 872 } 873 if (rf & 2) { 874 \u0275\u0275advance(); 875 \u0275\u0275classProp("has-photo", ctx.currentPhoto); 876 \u0275\u0275advance(); 877 \u0275\u0275conditional(ctx.currentPhoto ? 2 : 3); 878 \u0275\u0275advance(2); 879 \u0275\u0275conditional(ctx.isCapturing ? 4 : -1); 880 \u0275\u0275advance(2); 881 \u0275\u0275property("disabled", ctx.isCapturing); 882 \u0275\u0275advance(2); 883 \u0275\u0275textInterpolate1(" ", \u0275\u0275pipeBind1(9, 8, "problemReport.photo.camera"), " "); 884 \u0275\u0275advance(2); 885 \u0275\u0275property("disabled", ctx.isCapturing); 886 \u0275\u0275advance(2); 887 \u0275\u0275textInterpolate1(" ", \u0275\u0275pipeBind1(13, 10, "problemReport.photo.gallery"), " "); 888 } 889}, dependencies: [ 890 IonButton, 891 IonSpinner, 892 TranslateModule, 893 TranslatePipe, 894 NgIcon 895], styles: ["\n\n.photo-capture-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.photo-preview[_ngcontent-%COMP%] {\n position: relative;\n width: 100%;\n aspect-ratio: 16/9;\n background: var(--ion-color-light);\n border-radius: 12px;\n overflow: hidden;\n border: 2px dashed var(--ion-color-medium);\n}\n.photo-preview.has-photo[_ngcontent-%COMP%] {\n border-style: solid;\n border-color: var(--ion-color-primary);\n}\n.photo-preview[_ngcontent-%COMP%] img[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n.photo-preview[_ngcontent-%COMP%] .placeholder[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--ion-color-medium);\n gap: 8px;\n}\n.photo-preview[_ngcontent-%COMP%] .placeholder[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n.photo-preview[_ngcontent-%COMP%] .remove-button[_ngcontent-%COMP%] {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.6);\n border: none;\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;
895\n cursor: pointer;\n}\n.photo-preview[_ngcontent-%COMP%] .remove-button[_ngcontent-%COMP%]:hover {\n background: rgba(0, 0, 0, 0.8);\n}\n.photo-preview[_ngcontent-%COMP%] .capturing-overlay[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.photo-preview[_ngcontent-%COMP%] .capturing-overlay[_ngcontent-%COMP%] ion-spinner[_ngcontent-%COMP%] {\n color: white;\n --color: white;\n}\n.capture-buttons[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n justify-content: center;\n}\n.capture-buttons[_ngcontent-%COMP%] ion-button[_ngcontent-%COMP%] {\n flex: 1;\n max-width: 150px;\n}\n.capture-buttons[_ngcontent-%COMP%] ion-button[_ngcontent-%COMP%] ng-icon[_ngcontent-%COMP%] {\n margin-right: 4px;\n}"] }); 896var PhotoCaptureComponent = _PhotoCaptureComponent; 897(() => { 898 (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(PhotoCaptureComponent, [{ 899 type: Component, 900 args: [{ selector: "app-photo-capture", standalone: true, imports: [ 901 IonButton, 902 IonSpinner, 903 TranslateModule, 904 NgIcon 905 ], viewProviders: [provideIcons({ lucideCamera, lucideImage, lucideX })], template: `<div class="photo-capture-container"> 906 <div class="photo-preview" [class.has-photo]="currentPhoto"> 907 @if (currentPhoto) { 908 <img [src]="currentPhoto" alt="Problem photo" /> 909 <button class="remove-button" (click)="removePhoto()" type="button"> 910 <ng-icon name="lucideX" size="16" /> 911 </button> 912 } @else { 913 <div class="placeholder" (click)="captureFromCamera()"> 914 <ng-icon name="lucideCamera" size="48" /> 915 <span>{{ 'problemReport.photo.placeholder' | translate }}</span> 916 </div> 917 } 918 919 @if (isCapturing) { 920 <div class="capturing-overlay"> 921 <ion-spinner name="crescent" /> 922 </div> 923 } 924 </div> 925 926 <div class="capture-buttons"> 927 <ion-button 928 fill="outline" 929 size="small" 930 [disabled]="isCapturing" 931 (click)="captureFromCamera()"> 932 <ng-icon name="lucideCamera" slot="start" /> 933 {{ 'problemReport.photo.camera' | translate }} 934 </ion-button> 935 936 <ion-button 937 fill="outline" 938 size="small" 939 [disabled]="isCapturing" 940 (click)="captureFromGallery()"> 941 <ng-icon name="lucideImage" slot="start" /> 942 {{ 'problemReport.photo.gallery' | translate }} 943 </ion-button> 944 </div> 945</div> 946`, styles: ["/* src/app/shared/components/photo-capture/photo-capture.component.scss */\n.photo-capture-container {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.photo-preview {\n position: relative;\n width: 100%;\n aspect-ratio: 16/9;\n background: var(--ion-color-light);\n border-radius: 12px;\n overflow: hidden;\n border: 2px dashed var(--ion-color-medium);\n}\n.photo-preview.has-photo {\n border-style: solid;\n border-color: var(--ion-color-primary);\n}\n.photo-preview img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n.photo-preview .placeholder {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--ion-color-medium);\n gap: 8px;\n}\n.photo-preview .placeholder span {\n font-size: 14px;\n}\n.photo-preview .remove-button {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.6);\n border: none;\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n.photo-preview .remove-button:hover {\n background: rgba(0, 0, 0, 0.8);\n}\n.photo-preview .capturing-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.photo-preview .capturing-overlay ion-spinner {\n color: white;\n --color: white;\n}\n.capture-buttons {\n display: flex;\n gap: 12px;\n justify-content: center;\n}\n.capture-buttons ion-button {\n flex: 1;\n max-width: 150px;\n}
946\n.capture-buttons ion-button ng-icon {\n margin-right: 4px;\n}\n"] }] 947 }], null, { currentPhoto: [{ 948 type: Input 949 }], photoCapture: [{ 950 type: Output 951 }], photoRemove: [{ 952 type: Output 953 }] }); 954})(); 955(() => { 956 (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(PhotoCaptureComponent, { className: "PhotoCaptureComponent", filePath: "src/app/shared/components/photo-capture/photo-capture.component.ts", lineNumber: 19 }); 957})(); 958
959// src/app/services/problem-report/problem-report-queue.service.ts 960var QUEUE_STORAGE_KEY = "problem_report_queue"; 961var MAX_QUEUE_SIZE = 10; 962var MAX_RETRY_COUNT = 3; 963var _ProblemReportQueueService = class _ProblemReportQueueService { 964 constructor(storageService, apiService, stateService) { 965 this.storageService = storageService; 966 this.apiService = apiService; 967 this.stateService = stateService; 968 this.destroy$ = new Subject(); 969 this.queue$ = new BehaviorSubject([]); 970 this.isOnline$ = new BehaviorSubject(true); 971 this.isSyncing$ = new BehaviorSubject(false); 972 this.init(); 973 } 974 ngOnDestroy() { 975 this.destroy$.next(); 976 this.destroy$.complete(); 977 this.removeNetworkListener(); 978 } 979 /** 980 * Initialize the queue service. 981 */ 982 init() { 983 return __async(this, null, function* () { 984 yield this.loadQueue(); 985 const status = yield Network.getStatus(); 986 this.isOnline$.next(status.connected); 987 this.networkListenerHandle = yield Network.addListener("networkStatusChange", (status2) => { 988 const wasOffline = !this.isOnline$.value; 989 this.isOnline$.next(status2.connected); 990 if (status2.connected && wasOffline) { 991 console.log("[ProblemReportQueue] Network restored, starting sync..."); 992 this.processQueue(); 993 } 994 }); 995 if (this.isOnline$.value && this.queue$.value.length > 0) { 996 this.processQueue(); 997 } 998 }); 999 } 1000 /** 1001 * Remove network listener on cleanup. 1002 */ 1003 removeNetworkListener() { 1004 return __async(this, null, function* () { 1005 if (this.networkListenerHandle) { 1006 yield this.networkListenerHandle.remove(); 1007 } 1008 }); 1009 } 1010 /** 1011 * Load queue from local storage. 1012 */ 1013 loadQueue() { 1014 return __async(this, null, function* () { 1015 try { 1016 const stored = yield this.storageService.get(QUEUE_STORAGE_KEY); 1017 if (stored) { 1018 const queue = JSON.parse(stored); 1019 this.queue$.next(queue); 1020 this.updateStateService(); 1021 } 1022 } catch (error) { 1023 console.error("[ProblemReportQueue] Failed to load queue:", error); 1024 this.queue$.next([]); 1025 } 1026 }); 1027 } 1028 /** 1029 * Save queue to local storage. 1030 */ 1031 saveQueue() { 1032 return __async(this, null, function* () { 1033 try { 1034 yield this.storageService.set(QUEUE_STORAGE_KEY, JSON.stringify(this.queue$.value)); 1035 this.updateStateService(); 1036 } catch (error) { 1037 console.error("[ProblemReportQueue] Failed to save queue:", error); 1038 } 1039 }); 1040 } 1041 /** 1042 * Update the state service with queue info. 1043 */ 1044 updateStateService() { 1045 this.stateService.updateQueueStatus(this.queue$.value.length); 1046 } 1047 /** 1048 * Get the current queue as observable. 1049 */ 1050 get queue() { 1051 return this.queue$.asObservable(); 1052 } 1053 /** 1054 * Get the current queue count. 1055 */ 1056 get queueCount() { 1057 return this.queue$.value.length; 1058 } 1059 /** 1060 * Check if currently online. 1061 */ 1062 get isOnline() { 1063 return this.isOnline$.value; 1064 } 1065 /** 1066 * Check if currently syncing. 1067 */ 1068 get isSyncing() { 1069 return this.isSyncing$.value; 1070 } 1071 /** 1072 * Check if queue is full. 1073 */ 1074 get isQueueFull() { 1075 return this.queue$.value.length >= MAX_QUEUE_SIZE; 1076 } 1077 /** 1078 * Add a report to the offline queue. 1079 * Returns true if added successfully, false if queue is full. 1080 */ 1081 addToQueue(title, description, category, severity, campusId, photoBase64, locationNodeId, buildingId, floorId, locationX, locationY, gpsLatitude, gpsLongitude) { 1082 return __async(this, null, function* () { 1083 if (this.isQueueFull) { 1084 console.warn("[ProblemReportQueue] Queue is full, cannot add report"); 1085 return false; 1086 } 1087 const offlineReport = { 1088 localId: this.generateLocalId(), 1089 photoBase64, 1090 title, 1091 description, 1092 category, 1093 severity, 1094 locationNodeId, 1095 campusId, 1096 buildingId, 1097 floorId, 1098 locationX, 1099 locationY, 1100 gpsLatitude, 1101 gpsLongitude, 1102 createdAt: (/* @__PURE__ */ new Date()).toISOString(), 1103 retryCount: 0 1104 }; 1105 const currentQueue = this.queue$.value; 1106 this.queue$.next([...currentQueue, offlineReport]); 1107 yield this.saveQueue(); 1108 console.log("[ProblemReportQueue] Report added to queue:", offlineReport.localId); 1109 return true; 1110 }); 1111 } 1112 /** 1113 * Remove a report from the queue. 1114 */ 1115 removeFromQueue(localId) { 1116 return __async(this, null, function* () { 1117 const currentQueue = this.queue$.value; 1118 const filtered = currentQueue.filter((r) => r.localId !== localId); 1119 this.queue$.next(filtered); 1120 yield this.saveQueue(); 1121 }); 1122 } 1123 /** 1124 * Process the queue - attempt to submit all pending reports. 1125 */ 1126 processQueue() { 1127 return __async(this, null, function* () { 1128 if (this.isSyncing$.value) { 1129 console.log("[ProblemReportQueue] Already syncing, skipping"); 1130 return; 1131 } 1132 if (!this.isOnline$.value) { 1133 console.log("[ProblemReportQueue] Offline, skipping sync"); 1134 return; 1135 } 1136 const queue = this.queue$.value; 1137 if (queue.length === 0) { 1138 console.log("[ProblemReportQueue] Queue empty, nothing to sync"); 1139 return; 1140 } 1141 this.isSyncing$.next(true); 1142 console.log(`[ProblemReportQueue] Processing ${queue.length} queued reports...`); 1143 from(queue).pipe(concatMap((report) => this.submitQueuedReport(report).pipe( 1144 delay(500) 1145 // Small delay between submissions 1146 )), takeUntil(this.destroy$)).subscribe({ 1147 complete: () => { 1148 this.isSyncing$.next(false); 1149 console.log("[ProblemReportQueue] Queue processing complete"); 1150 }, 1151 error: (error) => { 1152 this.isSyncing$.next(false); 1153 console.error("[ProblemReportQueue] Queue processing error:", error); 1154 } 1155 }); 1156 }); 1157 } 1158 /** 1159 * Submit a single queued report. 1160 */ 1161 submitQueuedReport(report) { 1162 if (report.photoBase64) { 1163 return this.apiService.submitProblemReportWithPhoto(report.title, report.description || void 0, report.category, report.severity, report.campusId, report.photoBase64, report.locationNodeId, report.buildingId, report.floorId, report.locationX, report.locationY, report.gpsLatitude, report.gpsLongitude).pipe(tap(() => __async(this, null, function* () { 1164 console.log(`[ProblemReportQueue] Report ${report.localId} submitted successfully`); 1165 yield this.removeFromQueue(report.localId); 1166 })), catchError((error) => __async(this, null, function* () { 1167 console.error(`[ProblemReportQueue] Failed to submit report ${report.localId}:`, error); 1168 report.retryCount++; 1169 if (report.retryCount >= MAX_RETRY_COUNT) { 1170 console.warn(`[ProblemReportQueue] Report ${report.localId} exceeded max retries, removing`); 1171 yield this.removeFromQueue(report.localId); 1172 } else { 1173 yield this.updateReportInQueue(report); 1174 } 1175 return of(null); 1176 }))); 1177 } else { 1178 return this.apiService.submitProblemReport(report.title, report.description || void 0, report.category, report.severity, report.campusId, report.locationNodeId, report.buildingId, report.floorId, report.locationX, report.locationY, report.gpsLatitude, report.gpsLongitude).pipe(tap(() => __async(this, null, function* () { 1179 console.log(`[ProblemReportQueue] Report ${report.localId} submitted successfully`); 1180 yield this.removeFromQueue(report.localId); 1181 })), catchError((error) => __async(this, null, function* () { 1182 console.error(`[ProblemReportQueue] Failed to submit report ${report.localId}:`, error); 1183 report.retryCount++; 1184 if (report.retryCount >= MAX_RETRY_COUNT) { 1185 console.warn(`[ProblemReportQueue] Report ${report.localId} exceeded max retries, removing`); 1186 yield this.removeFromQueue(report.localId); 1187 } else { 1188 yield this.updateReportInQueue(report); 1189 } 1190 return of(null); 1191 }))); 1192 } 1193 } 1194 /** 1195 * Update a report in the queue (for retry count updates). 1196 */ 1197 updateReportInQueue(report) { 1198 return __async(this, null, function* () { 1199 const currentQueue = this.queue$.value; 1200 const index = currentQueue.findIndex((r) => r.localId === report.localId); 1201 if (index >= 0) { 1202 currentQueue[index] = report; 1203 this.queue$.next([...currentQueue]); 1204 yield this.saveQueue(); 1205 } 1206 }); 1207 } 1208 /** 1209 * Generate a unique local ID for offline reports. 1210 */ 1211 generateLocalId() { 1212 return `local_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; 1213 } 1214 /** 1215 * Clear the entire queue (use with caution). 1216 */ 1217 clearQueue() { 1218 return __async(this, null, function* () { 1219 this.queue$.next([]); 1220 yield this.saveQueue(); 1221 console.log("[ProblemReportQueue] Queue cleared"); 1222 }); 1223 } 1224 /** 1225 * Force a sync attempt (manual trigger). 1226 */ 1227 forceSync() { 1228 return __async(this, null, function* () { 1229 const status = yield Network.getStatus(); 1230 this.isOnline$.next(status.connected); 1231 if (status.connected) { 1232 yield this.processQueue(); 1233 } 1234 }); 1235 } 1236}; 1237_ProblemReportQueueService.\u0275fac = function ProblemReportQueueService_Factory(__ngFactoryType__) { 1238 return new (__ngFactoryType__ || _ProblemReportQueueService)(\u0275\u0275inject(StorageService), \u0275\u0275inject(ApiService), \u0275\u0275inject(ProblemReportStateService)); 1239}; 1240_ProblemReportQueueService.\u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _ProblemReportQueueService, factory: _ProblemReportQueueService.\u0275fac, providedIn: "root" }); 1241var ProblemReportQueueService = _ProblemReportQueueService; 1242(() => { 1243 (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ProblemReportQueueService, [{ 1244 type: Injectable, 1245 args: [{ 1246 providedIn: "root" 1247 }] 1248 }], () => [{ type: StorageService }, { type: ApiService }, { type: ProblemReportStateService }], null); 1249})(); 1250
1251// src/app/pages/problem-report/problem-report.page.ts 1252var _c0 = (a0) => ({ count: a0 }); 1253var _forTrack0 = ($index, $item) => $item.value; 1254function ProblemReportPage_Conditional_9_Template(rf, ctx) { 1255 if (rf & 1) { 1256 \u0275\u0275elementStart(0, "div", 3); 1257 \u0275\u0275element(1, "ng-icon", 23); 1258 \u0275\u0275elementStart(2, "span"); 1259 \u0275\u0275text(3); 1260 \u0275\u0275pipe(4, "translate"); 1261 \u0275\u0275elementEnd()(); 1262 } 1263 if (rf & 2) { 1264 \u0275\u0275advance(3); 1265 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(4, 1, "problemReport.offline.message")); 1266 } 1267} 1268function ProblemReportPage_Conditional_10_Template(rf, ctx) { 1269 if (rf & 1) { 1270 \u0275\u0275elementStart(0, "div", 4); 1271 \u0275\u0275element(1, "ng-icon", 24); 1272 \u0275\u0275elementStart(2, "span"); 1273 \u0275\u0275text(3); 1274 \u0275\u0275pipe(4, "translate"); 1275 \u0275\u0275elementEnd()(); 1276 } 1277 if (rf & 2) { 1278 const ctx_r0 = \u0275\u0275nextContext(); 1279 \u0275\u0275advance(3); 1280 \u0275\u0275textInterpolate(\u0275\u0275pipeBind2(4, 1, "problemReport.queue.pending", \u0275\u0275pureFunction1(4, _c0, ctx_r0.queuedCount))); 1281 } 1282} 1283function ProblemReportPage_Conditional_25_Template(rf, ctx) { 1284 if (rf & 1) { 1285 \u0275\u0275elementStart(0, "span", 11); 1286 \u0275\u0275text(1); 1287 \u0275\u0275pipe(2, "translate"); 1288 \u0275\u0275elementEnd(); 1289 } 1290 if (rf & 2) { 1291 \u0275\u0275advance(); 1292 \u0275\u0275textInterpolate1("(", \u0275\u0275pipeBind1(2, 1, "problemReport.title.minChars"), ")"); 1293 } 1294} 1295function ProblemReportPage_For_33_Template(rf, ctx) { 1296 if (rf & 1) { 1297 \u0275\u0275elementStart(0, "ion-select-option", 13); 1298 \u0275\u0275text(1); 1299 \u0275\u0275pipe(2, "translate"); 1300 \u0275\u0275elementEnd(); 1301 } 1302 if (rf & 2) { 1303 const cat_r2 = ctx.$implicit; 1304 \u0275\u0275property("value", cat_r2.value); 1305 \u0275\u0275advance(); 1306 \u0275\u0275textInterpolate1(" ", \u0275\u0275pipeBind1(2, 2, cat_r2.labelKey), " "); 1307 } 1308} 1309function ProblemReportPage_For_41_Template(rf, ctx) { 1310 if (rf & 1) { 1311 \u0275\u0275elementStart(0, "ion-select-option", 13); 1312 \u0275\u0275text(1); 1313 \u0275\u0275pipe(2, "translate"); 1314 \u0275\u0275elementEnd(); 1315 } 1316 if (rf & 2) { 1317 const sev_r3 = ctx.$implicit; 1318 \u0275\u0275property("value", sev_r3.value); 1319 \u0275\u0275advance(); 1320 \u0275\u0275textInterpolate1(" ", \u0275\u0275pipeBind1(2, 2, sev_r3.labelKey), " "); 1321 } 1322} 1323function ProblemReportPage_Conditional_57_Template(rf, ctx) { 1324 if (rf & 1) { 1325 \u0275\u0275elementStart(0, "div", 19); 1326 \u0275\u0275element(1, "ng-icon", 25); 1327 \u0275\u0275elementStart(2, "span", 26); 1328 \u0275\u0275text(3); 1329 \u0275\u0275elementEnd()(); 1330 } 1331 if (rf & 2) { 1332 const ctx_r0 = \u0275\u0275nextContext(); 1333 \u0275\u0275advance(3); 1334 \u0275\u0275textInterpolate(ctx_r0.locationDescription); 1335 } 1336} 1337function ProblemReportPage_Conditional_58_Template(rf, ctx) { 1338 if (rf & 1) { 1339 \u0275\u0275elementStart(0, "div", 19); 1340 \u0275\u0275element(1, "ng-icon", 27); 1341 \u0275\u0275elementStart(2, "span", 26); 1342 \u0275\u0275text(3); 1343 \u0275\u0275elementEnd()(); 1344 } 1345 if (rf & 2) { 1346 const ctx_r0 = \u0275\u0275nextContext(); 1347 \u0275\u0275advance(3); 1348 \u0275\u0275textInterpolate(ctx_r0.campusName); 1349 } 1350} 1351function ProblemReportPage_Conditional_59_Template(rf, ctx) { 1352 if (rf & 1) { 1353 \u0275\u0275elementStart(0, "div", 20); 1354 \u0275\u0275element(1, "ng-icon", 28); 1355 \u0275\u0275elementStart(2, "span", 26); 1356 \u0275\u0275text(3); 1357 \u0275\u0275pipe(4, "translate"); 1358 \u0275\u0275elementEnd()(); 1359 } 1360 if (rf & 2) { 1361 \u0275\u0275advance(3); 1362 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(4, 1, "problemReport.location.unknown")); 1363 } 1364} 1365function ProblemReportPage_Conditional_62_Template(rf, ctx) { 1366 if (rf & 1) { 1367 \u0275\u0275element(0, "ion-spinner", 29); 1368 \u0275\u0275elementStart(1, "span"); 1369 \u0275\u0275text(2); 1370 \u0275\u0275pipe(3, "translate"); 1371 \u0275\u0275elementEnd(); 1372 } 1373 if (rf & 2) { 1374 \u0275\u0275advance(2); 1375 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(3, 1, "problemReport.button.submitting")); 1376 } 1377} 1378function ProblemReportPage_Conditional_63_Template(rf, ctx) { 1379 if (rf & 1) { 1380 \u0275\u0275element(0, "ng-icon", 30); 1381 \u0275\u0275elementStart(1, "span"); 1382 \u0275\u0275text(2); 1383 \u0275\u0275pipe(3, "translate"); 1384 \u0275\u0275elementEnd(); 1385 } 1386 if (rf & 2) { 1387 \u0275\u0275advance(2); 1388 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(3, 1, "problemReport.button.queue")); 1389 } 1390} 1391function ProblemReportPage_Conditional_64_Template(rf, ctx) { 1392 if (rf & 1) { 1393 \u0275\u0275element(0, "ng-icon", 31); 1394 \u0275\u0275elementStart(1, "span"); 1395 \u0275\u0275text(2); 1396 \u0275\u0275pipe(3, "translate"); 1397 \u0275\u0275elementEnd(); 1398 } 1399 if (rf & 2) { 1400 \u0275\u0275advance(2); 1401 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(3, 1, "problemReport.button.submit")); 1402 } 1403} 1404var _ProblemReportPage = class _ProblemReportPage { 1405 constructor(router, apiService, stateService, queueService, locationService, gpsService, toastService, databaseService, translateService) { 1406 this.router = router; 1407 this.apiService = apiService; 1408 this.stateService = stateService; 1409 this.queueService = queueService; 1410 this.locationService = locationService; 1411 this.gpsService = gpsService; 1412 this.toastService = toastService; 1413 this.databaseService = databaseService; 1414 this.translateService = translateService; 1415 this.destroy$ = new Subject(); 1416 this.photoBase64 = null; 1417 this.title = ""; 1418 this.description = ""; 1419 this.category = "other"; 1420 this.severity = "medium"; 1421 this.isSubmitting = false;
1422 this.isOnline = true; 1423 this.queuedCount = 0; 1424 this.locationDescription = ""; 1425 this.campusId = null; 1426 this.campusName = null; 1427 this.buildingId = null; 1428 this.floorId = null; 1429 this.locationX = null; 1430 this.locationY = null; 1431 this.gpsLatitude = null; 1432 this.gpsLongitude = null; 1433 this.categories = PROBLEM_REPORT_CATEGORIES; 1434 this.severities = PROBLEM_SEVERITIES; 1435 } 1436 ngOnInit() { 1437 this.stateService.state$.pipe(takeUntil(this.destroy$)).subscribe((state) => { 1438 this.isSubmitting = state.isSubmitting; 1439 this.queuedCount = state.queuedReportCount; 1440 }); 1441 this.isOnline = this.queueService.isOnline; 1442 this.locationService.beaconLocation$.pipe(takeUntil(this.destroy$)).subscribe((location) => { 1443 if (location) { 1444 this.updateLocationFromBeacon(location); 1445 } 1446 }); 1447 this.gpsService.position$.pipe(takeUntil(this.destroy$)).subscribe((gpsPosition) => { 1448 if (gpsPosition) { 1449 this.gpsLatitude = gpsPosition.latitude; 1450 this.gpsLongitude = gpsPosition.longitude; 1451 } 1452 }); 1453 this.updateLocationInfo(); 1454 } 1455 ngOnDestroy() { 1456 this.destroy$.next(); 1457 this.destroy$.complete(); 1458 } 1459 updateLocationFromBeacon(location) { 1460 var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; 1461 const parts = []; 1462 if ((_b = (_a = location.floor) == null ? void 0 : _a.entity) == null ? void 0 : _b.name) { 1463 parts.push(location.floor.entity.name); 1464 } 1465 if ((_d = (_c = location.building) == null ? void 0 : _c.entity) == null ? void 0 : _d.name) { 1466 parts.push(location.building.entity.name); 1467 } 1468 this.locationDescription = parts.join(", ") || ""; 1469 if ((_e = location.campus) == null ? void 0 : _e.entity) { 1470 this.campusId = (_f = location.campus.entity.id) != null ? _f : null; 1471 this.campusName = (_g = location.campus.entity.name) != null ? _g : null; 1472 } 1473 if ((_i = (_h = location.building) == null ? void 0 : _h.entity) == null ? void 0 : _i.id) { 1474 this.buildingId = location.building.entity.id; 1475 } 1476 if ((_k = (_j = location.floor) == null ? void 0 : _j.entity) == null ? void 0 : _k.id) { 1477 this.floorId = location.floor.entity.id; 1478 } 1479 this.locationX = location.point.x != null ? Math.round(location.point.x) : null; 1480 this.locationY = location.point.y != null ? Math.round(location.point.y) : null; 1481 } 1482 updateLocationInfo() { 1483 var _a, _b; 1484 const location = this.locationService.currentBeaconLocation; 1485 if (location) { 1486 this.updateLocationFromBeacon(location); 1487 } 1488 const gpsPosition = this.gpsService.currentPosition; 1489 if (gpsPosition) { 1490 this.gpsLatitude = gpsPosition.latitude; 1491 this.gpsLongitude = gpsPosition.longitude; 1492 } 1493 if (!this.campusId) { 1494 const campuses = Array.from(this.databaseService.Models.values()).filter((m) => m.type === "CAMPUS"); 1495 if (campuses.length > 0) { 1496 this.campusId = (_a = campuses[0].entity.id) != null ? _a : null; 1497 this.campusName = (_b = campuses[0].entity.name) != null ? _b : null; 1498 } 1499 } 1500 } 1501 onPhotoCapture(photoBase64) { 1502 this.photoBase64 = photoBase64; 1503 } 1504 onPhotoRemove() { 1505 this.photoBase64 = null; 1506 } 1507 get isFormValid() { 1508 return this.title.trim().length >= 5 && !!this.campusId; 1509 } 1510 get titleLength() { 1511 return this.title.length; 1512 } 1513 get descriptionLength() { 1514 return this.description.length; 1515 } 1516 submit() { 1517 return __async(this, null, function* () { 1518 var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l; 1519 if (!this.isFormValid || this.isSubmitting) 1520 return; 1521 if (!this.campusId) { 1522 this.toastService.showToast({ 1523 message: this.translateService.instant("problemReport.error.noCampus"), 1524 type: "error" 1525 }); 1526 return; 1527 } 1528 this.stateService.setSubmitting(true);
1529 try { 1530 if (this.queueService.isOnline) { 1531 if (this.photoBase64) { 1532 yield firstValueFrom(this.apiService.submitProblemReportWithPhoto(this.title.trim(), this.description.trim() || void 0, this.category, this.severity, this.campusId, this.photoBase64, void 0, (_a = this.buildingId) != null ? _a : void 0, (_b = this.floorId) != null ? _b : void 0, (_c = this.locationX) != null ? _c : void 0, (_d = this.locationY) != null ? _d : void 0, (_e = this.gpsLatitude) != null ? _e : void 0, (_f = this.gpsLongitude) != null ? _f : void 0)); 1533 } else { 1534 yield firstValueFrom(this.apiService.submitProblemReport(this.title.trim(), this.description.trim() || void 0, this.category, this.severity, this.campusId, void 0, (_g = this.buildingId) != null ? _g : void 0, (_h = this.floorId) != null ? _h : void 0, (_i = this.locationX) != null ? _i : void 0, (_j = this.locationY) != null ? _j : void 0, (_k = this.gpsLatitude) != null ? _k : void 0, (_l = this.gpsLongitude) != null ? _l : void 0)); 1535 } 1536 this.handleSuccess("problemReport.toast.submitSuccess"); 1537 } else { 1538 yield this.queueReport(); 1539 } 1540 } catch (error) { 1541 console.error("Submit error:", error); 1542 const httpError = error;
1543 if (httpError.status && httpError.status >= 400 && httpError.status < 500) { 1544 this.handleError(error, "problemReport.toast.validationError"); 1545 } else if (httpError.status && httpError.status >= 500) { 1546 this.handleError(error, "problemReport.toast.serverError"); 1547 } else { 1548 yield this.queueReportAsFallback(); 1549 } 1550 } finally { 1551 this.stateService.setSubmitting(false); 1552 } 1553 }); 1554 } 1555 queueReport() { 1556 return __async(this, null, function* () { 1557 var _a, _b, _c, _d, _e, _f, _g; 1558 if (!this.campusId) 1559 return; 1560 const success = yield this.queueService.addToQueue(this.title.trim(), this.description.trim(), this.category, this.severity, this.campusId, (_a = this.photoBase64) != null ? _a : void 0, void 0, (_b = this.buildingId) != null ? _b : void 0, (_c = this.floorId) != null ? _c : void 0, (_d = this.locationX) != null ? _d : void 0, (_e = this.locationY) != null ? _e : void 0, (_f = this.gpsLatitude) != null ? _f : void 0, (_g = this.gpsLongitude) != null ? _g : void 0); 1561 if (success) { 1562 this.handleSuccess("problemReport.toast.queueSuccess"); 1563 } else { 1564 this.toastService.showToast({ 1565 message: this.translateService.instant("problemReport.toast.queueFull"), 1566 type: "error" 1567 }); 1568 } 1569 }); 1570 } 1571 queueReportAsFallback() { 1572 return __async(this, null, function* () { 1573 var _a, _b, _c, _d, _e, _f, _g; 1574 if (!this.campusId) { 1575 this.handleError(new Error("No campus"), "problemReport.error.noCampus"); 1576 return; 1577 } 1578 const success = yield this.queueService.addToQueue(this.title.trim(), this.description.trim(), this.category, this.severity, this.campusId, (_a = this.photoBase64) != null ? _a : void 0, void 0, (_b = this.buildingId) != null ? _b : void 0, (_c = this.floorId) != null ? _c : void 0, (_d = this.locationX) != null ? _d : void 0, (_e = this.locationY) != null ? _e : void 0, (_f = this.gpsLatitude) != null ? _f : void 0, (_g = this.gpsLongitude) != null ? _g : void 0); 1579 if (success) { 1580 this.handleSuccess("problemReport.toast.queuedAfterFail"); 1581 } else { 1582 this.handleError(new Error("Queue failed"), "problemReport.toast.submitError"); 1583 } 1584 }); 1585 } 1586 handleSuccess(toastKey) { 1587 this.stateService.setSubmitSuccess(); 1588 this.stateService.refreshTrigger.emit(); 1589 this.toastService.showToast({ 1590 message: this.translateService.instant(toastKey), 1591 type: "success" 1592 }); 1593 this.resetForm(); 1594 this.router.navigate(["/navigation"]); 1595 } 1596 handleError(error, toastKey) { 1597 const errorMessage = error instanceof Error ? error.message : "Submission failed"; 1598 console.error("Submit error:", errorMessage); 1599 this.stateService.setSubmitError(errorMessage); 1600 this.toastService.showToast({ 1601 message: this.translateService.instant(toastKey), 1602 type: "error" 1603 }); 1604 } 1605 resetForm() { 1606 this.photoBase64 = null; 1607 this.title = ""; 1608 this.description = ""; 1609 this.category = "other"; 1610 this.severity = "medium"; 1611 this.buildingId = null; 1612 this.floorId = null; 1613 this.locationX = null; 1614 this.locationY = null; 1615 this.gpsLatitude = null; 1616 this.gpsLongitude = null; 1617 } 1618 getCategoryIcon(category) { 1619 var _a; 1620 const option = this.categories.find((c) => c.value === category); 1621 return (_a = option == null ? void 0 : option.icon) != null ? _a : "lucideCircleHelp"; 1622 } 1623 getSeverityColor(severity) { 1624 var _a; 1625 const option = this.severities.find((s) => s.value === severity); 1626 return (_a = option == null ? void 0 : option.color) != null ? _a : "medium"; 1627 } 1628}; 1629_ProblemReportPage.\u0275fac = function ProblemReportPage_Factory(__ngFactoryType__) { 1630 return new (__ngFactoryType__ || _ProblemReportPage)(\u0275\u0275directiveInject(Router), \u0275\u0275directiveInject(ApiService), \u0275\u0275directiveInject(ProblemReportStateService), \u0275\u0275directiveInject(ProblemReportQueueService), \u0275\u0275directiveInject(LocationService), \u0275\u0275directiveInject(GPSService), \u0275\u0275directiveInject(ToastService), \u0275\u0275directiveInject(DatabaseService), \u0275\u0275directiveInject(TranslateService)); 1631}; 1632_ProblemReportPage.\u0275cmp = /* @__PURE__ */ \u0275\u0275defineComponent({ type: _ProblemReportPage, selectors: [["app-problem-report"]], features: [\u0275\u0275ProvidersFeature([], [provideIcons({ 1633 lucideMapPin, 1634 lucideSend, 1635 lucideWifi, 1636 lucideWifiOff, 1637 lucideZap, 1638 lucideDroplets, 1639 lucideBuilding, 1640 lucideAccessibility, 1641 lucideTriangleAlert, 1642 lucideSparkles, 1643 lucideCircleHelp 1644})])], decls: 65, vars: 60, consts: [["slot", "start"], ["defaultHref", "/navigation"], [1, "content-container"], [1, "offline-banner"], [1, "queued-banner"], [1, "report-form", 3, "ngSubmit"], [1, "form-section"], [1, "form-label"], [3, "photoCapture", "photoRemove", "currentPhoto"], ["name", "title", 1, "title-textarea", 3, "ngModelChange", "ngModel", "placeholder", "maxlength", "rows", "disabled", "autoGrow"], [1, "char-count"], [1, "min-hint"], ["name", "category", "interface", "action-sheet", 1, "category-select", 3, "ngModelChange", "ngModel", "placeholder", "disabled"], [3, "value"], ["name", "severity", "interface", "action-sheet", 1, "severity-select", 3, "ngModelChange", "ngModel", "placeholder", "disabled"], ["name", "description", 1, "description-textarea", 3, "ngModelChange", "ngModel", "placeholder", "maxlength", "rows", "disabled", "autoGrow"], [1, "form-section", "location-section"], [1, "location-card"], [1, "location-info"], [1, "location-item"], [1, "location-item", "unknown"], [1, "submit-section"], ["type", "submit", "expand", "block", 1, "submit-button", 3, "disabled"], ["name", "lucideWifiOff", "size", "1em"], ["name", "lucideWifi", "size", "1em"], ["name", "lucideMapPin", "size", "1em"], [1, "location-value"], ["name", "lucideBuilding", "size", "1em"], ["name", "lucideCircleHelp", "size", "1em"], ["name", "crescent", "slot", "start"], ["name", "lucideWifiOff", "slot", "start"], ["name", "lucideSend", "slot", "start"]], template: function ProblemReportPage_Template(rf, ctx) { 1645 if (rf & 1) {
1646 \u0275\u0275elementStart(0, "ion-header")(1, "ion-toolbar")(2, "ion-buttons", 0); 1647 \u0275\u0275element(3, "ion-back-button", 1); 1648 \u0275\u0275elementEnd(); 1649 \u0275\u0275elementStart(4, "ion-title"); 1650 \u0275\u0275text(5); 1651 \u0275\u0275pipe(6, "translate"); 1652 \u0275\u0275elementEnd()()(); 1653 \u0275\u0275elementStart(7, "ion-content")(8, "div", 2); 1654 \u0275\u0275template(9, ProblemReportPage_Conditional_9_Template, 5, 3, "div", 3)(10, ProblemReportPage_Conditional_10_Template, 5, 6, "div", 4); 1655 \u0275\u0275elementStart(11, "form", 5); 1656 \u0275\u0275listener("ngSubmit", function ProblemReportPage_Template_form_ngSubmit_11_listener() { 1657 return ctx.submit(); 1658 }); 1659 \u0275\u0275elementStart(12, "div", 6)(13, "label", 7); 1660 \u0275\u0275text(14); 1661 \u0275\u0275pipe(15, "translate"); 1662 \u0275\u0275elementEnd(); 1663 \u0275\u0275elementStart(16, "app-photo-capture", 8); 1664 \u0275\u0275listener("photoCapture", function ProblemReportPage_Template_app_photo_capture_photoCapture_16_listener($event) { 1665 return ctx.onPhotoCapture($event); 1666 })("photoRemove", function ProblemReportPage_Template_app_photo_capture_photoRemove_16_listener() { 1667 return ctx.onPhotoRemove(); 1668 }); 1669 \u0275\u0275elementEnd()(); 1670 \u0275\u0275elementStart(17, "div", 6)(18, "label", 7); 1671 \u0275\u0275text(19); 1672 \u0275\u0275pipe(20, "translate"); 1673 \u0275\u0275elementEnd(); 1674 \u0275\u0275elementStart(21, "ion-textarea", 9); 1675 \u0275\u0275pipe(22, "translate"); 1676 \u0275\u0275twoWayListener("ngModelChange", function ProblemReportPage_Template_ion_textarea_ngModelChange_21_listener($event) { 1677 \u0275\u0275twoWayBindingSet(ctx.title, $event) || (ctx.title = $event); 1678 return $event; 1679 }); 1680 \u0275\u0275elementEnd(); 1681 \u0275\u0275elementStart(23, "div", 10); 1682 \u0275\u0275text(24); 1683 \u0275\u0275template(25, ProblemReportPage_Conditional_25_Template, 3, 3, "span", 11); 1684 \u0275\u0275elementEnd()(); 1685 \u0275\u0275elementStart(26, "div", 6)(27, "label", 7); 1686 \u0275\u0275text(28); 1687 \u0275\u0275pipe(29, "translate"); 1688 \u0275\u0275elementEnd(); 1689 \u0275\u0275elementStart(30, "ion-select", 12); 1690 \u0275\u0275pipe(31, "translate"); 1691 \u0275\u0275twoWayListener("ngModelChange", function ProblemReportPage_Template_ion_select_ngModelChange_30_listener($event) { 1692 \u0275\u0275twoWayBindingSet(ctx.category, $event) || (ctx.category = $event); 1693 return $event; 1694 }); 1695 \u0275\u0275repeaterCreate(32, ProblemReportPage_For_33_Template, 3, 4, "ion-select-option", 13, _forTrack0); 1696 \u0275\u0275elementEnd()(); 1697 \u0275\u0275elementStart(34, "div", 6)(35, "label", 7); 1698 \u0275\u0275text(36); 1699 \u0275\u0275pipe(37, "translate"); 1700 \u0275\u0275elementEnd(); 1701 \u0275\u0275elementStart(38, "ion-select", 14); 1702 \u0275\u0275pipe(39, "translate"); 1703 \u0275\u0275twoWayListener("ngModelChange", function ProblemReportPage_Template_ion_select_ngModelChange_38_listener($event) { 1704 \u0275\u0275twoWayBindingSet(ctx.severity, $event) || (ctx.severity = $event); 1705 return $event; 1706 }); 1707 \u0275\u0275repeaterCreate(40, ProblemReportPage_For_41_Template, 3, 4, "ion-select-option", 13, _forTrack0); 1708 \u0275\u0275elementEnd()(); 1709 \u0275\u0275elementStart(42, "div", 6)(43, "label", 7); 1710 \u0275\u0275text(44); 1711 \u0275\u0275pipe(45, "translate"); 1712 \u0275\u0275elementEnd(); 1713 \u0275\u0275elementStart(46, "ion-textarea", 15); 1714 \u0275\u0275pipe(47, "translate"); 1715 \u0275\u0275twoWayListener("ngModelChange", function ProblemReportPage_Template_ion_textarea_ngModelChange_46_listener($event) { 1716 \u0275\u0275twoWayBindingSet(ctx.description, $event) || (ctx.description = $event); 1717 return $event; 1718 }); 1719 \u0275\u0275elementEnd(); 1720 \u0275\u0275elementStart(48, "div", 10); 1721 \u0275\u0275text(49); 1722 \u0275\u0275elementEnd()(); 1723 \u0275\u0275elementStart(50, "div", 16)(51, "label", 7); 1724 \u0275\u0275text(52); 1725 \u0275\u0275pipe(53, "translate"); 1726 \u0275\u0275elementEnd(); 1727 \u0275\u0275elementStart(54, "ion-card", 17)(55, "ion-card-content")(56, "div", 18); 1728 \u0275\u0275template(57, ProblemReportPage_Conditional_57_Template, 4, 1, "div", 19)(58, ProblemReportPage_Conditional_58_Template, 4, 1, "div", 19)(59, ProblemReportPage_Conditional_59_Template, 5, 3, "div", 20); 1729 \u0275\u0275elementEnd()()()(); 1730 \u0275\u0275elementStart(60, "div", 21)(61, "ion-button", 22); 1731 \u0275\u0275template(62, ProblemReportPage_Conditional_62_Template, 4, 3)(63, ProblemReportPage_Conditional_63_Template, 4, 3)(64, ProblemReportPage_Conditional_64_Template, 4, 3); 1732 \u0275\u0275elementEnd()()()()(); 1733 } 1734 if (rf & 2) { 1735 \u0275\u0275advance(5); 1736 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(6, 38, "problemReport.header")); 1737 \u0275\u0275advance(4); 1738 \u0275\u0275conditional(!ctx.isOnline ? 9 : -1); 1739 \u0275\u0275advance(); 1740 \u0275\u0275conditional(ctx.queuedCount > 0 ? 10 : -1); 1741 \u0275\u0275advance(4); 1742 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(15, 40, "problemReport.photo.label")); 1743 \u0275\u0275advance(2); 1744 \u0275\u0275property("currentPhoto", ctx.photoBase64); 1745 \u0275\u0275advance(3); 1746 \u0275\u0275textInterpolate1("", \u0275\u0275pipeBind1(20, 42, "problemReport.title.label"), " *"); 1747 \u0275\u0275advance(2); 1748 \u0275\u0275twoWayProperty("ngModel", ctx.title); 1749 \u0275\u0275property("placeholder", \u0275\u0275pipeBind1(22, 44, "problemReport.title.placeholder"))("maxlength", 200)("rows", 2)("disabled", ctx.isSubmitting)("autoGrow", true); 1750 \u0275\u0275advance(2); 1751 \u0275\u0275classProp("warning", ctx.titleLength < 5); 1752 \u0275\u0275advance(); 1753 \u0275\u0275textInterpolate1(" ", ctx.titleLength, "/200 "); 1754 \u0275\u0275advance(); 1755 \u0275\u0275conditional(ctx.titleLength > 0 && ctx.titleLength < 5 ? 25 : -1); 1756 \u0275\u0275advance(3); 1757 \u0275\u0275textInterpolate1("", \u0275\u0275pipeBind1(29, 46, "problemReport.category.label"), " *"); 1758 \u0275\u0275advance(2); 1759 \u0275\u0275twoWayProperty("ngModel", ctx.category); 1760 \u0275\u0275property("placeholder", \u0275\u0275pipeBind1(31, 48, "problemReport.category.placeholder"))("disabled", ctx.isSubmitting); 1761 \u0275\u0275advance(2); 1762 \u0275\u0275repeater(ctx.categories); 1763 \u0275\u0275advance(4); 1764 \u0275\u0275textInterpolate1("", \u0275\u0275pipeBind1(37, 50, "problemReport.severity.label"), " *"); 1765 \u0275\u0275advance(2); 1766 \u0275\u0275twoWayProperty("ngModel", ctx.severity); 1767 \u0275\u0275property("placeholder", \u0275\u0275pipeBind1(39, 52, "problemReport.severity.placeholder"))("disabled", ctx.isSubmitting); 1768 \u0275\u0275advance(2); 1769 \u0275\u0275repeater(ctx.severities); 1770 \u0275\u0275advance(4); 1771 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(45, 54, "problemReport.description.label")); 1772 \u0275\u0275advance(2); 1773 \u0275\u0275twoWayProperty("ngModel", ctx.description); 1774 \u0275\u0275property("placeholder", \u0275\u0275pipeBind1(47, 56, "problemReport.description.placeholder"))("maxlength", 2e3)("rows", 4)("disabled", ctx.isSubmitting)("autoGrow", true); 1775 \u0275\u0275advance(3); 1776 \u0275\u0275textInterpolate1(" ", ctx.descriptionLength, "/2000 "); 1777 \u0275\u0275advance(3); 1778 \u0275\u0275textInterpolate(\u0275\u0275pipeBind1(53, 58, "problemReport.location.label")); 1779 \u0275\u0275advance(5); 1780 \u0275\u0275conditional(ctx.locationDescription ? 57 : -1); 1781 \u0275\u0275advance(); 1782 \u0275\u0275conditional(ctx.campusName ? 58 : -1); 1783 \u0275\u0275advance(); 1784 \u0275\u0275conditional(!ctx.locationDescription && !ctx.campusName ? 59 : -1); 1785 \u0275\u0275advance(2); 1786 \u0275\u0275property("disabled", !ctx.isFormValid || ctx.isSubmitting); 1787 \u0275\u0275advance(); 1788 \u0275\u0275conditional(ctx.isSubmitting ? 62 : !ctx.isOnline ? 63 : 64); 1789 } 1790}, dependencies: [ 1791 FormsModule, 1792 \u0275NgNoValidate, 1793 NgControlStatus, 1794 NgControlStatusGroup, 1795 MaxLengthValidator, 1796 NgModel, 1797 NgForm, 1798 TranslateModule, 1799 TranslatePipe, 1800 NgIcon, 1801 PhotoCaptureComponent, 1802 IonHeader, 1803 IonToolbar, 1804 IonButtons, 1805 IonBackButton, 1806 IonTitle, 1807 IonContent, 1808 IonButton, 1809 IonSelect, 1810 IonSelectOption, 1811 IonTextarea,
1812 IonSpinner, 1813 IonCard, 1814 IonCardContent 1815], styles: ["\n\nion-content[_ngcontent-%COMP%] {\n --padding-start: 16px;\n --padding-end: 16px;\n --padding-top: 16px;\n --padding-bottom: 16px;\n}\nion-content[_ngcontent-%COMP%]::part(scroll) {\n overflow-x: hidden;\n}\n.content-container[_ngcontent-%COMP%] {\n max-width: 600px;\n margin: 0 auto;\n padding: 1rem;\n}\n.report-form[_ngcontent-%COMP%] {\n background: var(--ion-item-background);\n border-radius: 12px;\n padding: 1.5rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n.report-form[_ngcontent-%COMP%] .form-section[_ngcontent-%COMP%]:not(:last-child) {\n margin-bottom: 1.5rem;\n}\n.offline-banner[_ngcontent-%COMP%], \n.queued-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n border-radius: 8px;\n margin-bottom: 16px;\n font-size: 14px;\n font-weight: 500;\n}\n.offline-banner[_ngcontent-%COMP%] ng-icon[_ngcontent-%COMP%], \n.queued-banner[_ngcontent-%COMP%] ng-icon[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n.offline-banner[_ngcontent-%COMP%] {\n background: var(--ion-color-warning);\n color: var(--ion-color-warning-contrast);\n}\n.queued-banner[_ngcontent-%COMP%] {\n background: var(--ion-color-info);\n color: var(--ion-color-info-contrast);\n}\n.report-form[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n.form-section[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.form-section[_ngcontent-%COMP%] .form-label[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--ion-color-medium);\n}\n.form-section[_ngcontent-%COMP%] ion-textarea[_ngcontent-%COMP%] {\n --padding-top: 12px;\n --padding-bottom: 12px;\n --padding-start: 12px;\n --padding-end: 12px;\n --border-radius: 8px;\n --background: var(--ion-color-light);\n --border-color: var(--ion-color-step-150);\n border: 1px solid var(--ion-color-step-200);\n}\n.form-section[_ngcontent-%COMP%] ion-textarea.title-textarea[_ngcontent-%COMP%] {\n min-height: 60px;\n}\n.form-section[_ngcontent-%COMP%] ion-textarea.description-textarea[_ngcontent-%COMP%] {\n min-height: 100px;\n}\n.form-section[_ngcontent-%COMP%] ion-select[_ngcontent-%COMP%] {\n --padding-start: 12px;\n --padding-end: 12px;\n --min-height: 44px;\n --border-radius: 8px;\n --background: var(--ion-color-light);\n --border-color: var(--ion-color-step-200);\n border: 1px solid var(--ion-color-step-200);\n}\n.form-section[_ngcontent-%COMP%] ion-select.category-select[_ngcontent-%COMP%], \n.form-section[_ngcontent-%COMP%] ion-select.severity-select[_ngcontent-%COMP%] {\n min-height: 44px;\n}\n.form-section[_ngcontent-%COMP%] .char-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--ion-color-medium);\n text-align: right;\n}\n.form-section[_ngcontent-%COMP%] .char-count.warning[_ngcontent-%COMP%] {\n color: var(--ion-color-warning);\n}\n.form-section[_ngcontent-%COMP%] .char-count[_ngcontent-%COMP%] .min-hint[_ngcontent-%COMP%] {\n color: var(--ion-color-danger);\n}\n.location-section[_ngcontent-%COMP%] .location-card[_ngcontent-%COMP%] {\n --background: var(--ion-color-light);\n --border-radius: 8px;\n border: 1px solid var(--ion-color-step-200);\n}\n.location-section[_ngcontent-%COMP%] .location-card[_ngcontent-%COMP%] ion-card-content[_ngcontent-%COMP%] {\n padding: 12px !important;\n}\n.location-section[_ngcontent-%COMP%] .location-card[_ngcontent-%COMP%] .location-info[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.location-section[_ngcontent-%COMP%] .location-card[_ngcontent-%COMP%] .location-info[_ngcontent-%COMP%] .location-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n}\n.location-section[_ngcontent-%COMP%] .location-card[_ngcontent-%COMP%] .location-info[_ngcontent-%COMP%] .location-item[_ngcontent-%COMP%] ng-icon[_ngcontent-%COMP%] {\n flex-shrink: 0;\n color: var(--ion-color-medium);\n}\n.location-section[_ngcontent-%COMP%] .location-card[_ngcontent-%COMP%] .location-info[_ngcontent-%COMP%] .location-item[_ngcontent-%COMP%] .location-value[_ngcontent-%COMP%] {\n color: var(--ion-color-dark);\n font-weight: 500;\n}\n.location-section[_ngcontent-%COMP%] .location-card[_ngcontent-%COMP%] .location-info[_ngcontent-%COMP%] .location-item.gps-location[_ngcontent-%COMP%] .location-value[_ngcontent-%COMP%] {\n font-family: monospace;\n font-size: 13px;\n}\n.location-section[_ngcontent-%COMP%] .location-card[_ngcontent-%COMP%] .location-info[_ngcontent-%COMP%] .location-item.unknown[_ngcontent-%COMP%] .location-value[_ngcontent-%COMP%] {\n color: var(--ion-color-medium);\n font-style: italic;\n}\n.submit-section[_ngcontent-%COMP%] {\n margin-top: 8px;\n margin-bottom: 16px;\n}\n.submit-section[_ngcontent-%COMP%] .submit-button[_ngcontent-%COMP%] {\n --border-radius: 8px;\n --height: 48px;\n font-weight: 600;\n text-transform: none;\n}\n.submit-section[_ngcontent-%COMP%] .submit-button[_ngcontent-%COMP%] ion-spinner[_ngcontent-%COMP%] {\n color: currentColor;\n}"] }); 1816var ProblemReportPage = _ProblemReportPage; 1817(() => { 1818 (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ProblemReportPage, [{ 1819 type: Component, 1820 args: [{ selector: "app-problem-report", imports: [ 1821 FormsModule, 1822 TranslateModule, 1823 NgIcon, 1824 PhotoCaptureComponent, 1825 IonHeader, 1826 IonToolbar, 1827 IonButtons, 1828 IonBackButton, 1829 IonTitle, 1830 IonContent, 1831 IonButton, 1832 IonSelect, 1833 IonSelectOption, 1834 IonTextarea,
1835 IonSpinner, 1836 IonCard, 1837 IonCardContent 1838 ], viewProviders: [provideIcons({ 1839 lucideMapPin, 1840 lucideSend, 1841 lucideWifi, 1842 lucideWifiOff, 1843 lucideZap, 1844 lucideDroplets, 1845 lucideBuilding, 1846 lucideAccessibility, 1847 lucideTriangleAlert, 1848 lucideSparkles, 1849 lucideCircleHelp 1850 })], template: `<ion-header> 1851 <ion-toolbar> 1852 <ion-buttons slot="start"> 1853 <ion-back-button defaultHref="/navigation" /> 1854 </ion-buttons> 1855 <ion-title>{{ 'problemReport.header' | translate }}</ion-title> 1856 </ion-toolbar> 1857</ion-header> 1858 1859<ion-content> 1860 <div class="content-container"> 1861 <!-- Offline Banner --> 1862 @if (!isOnline) { 1863 <div class="offline-banner"> 1864 <ng-icon name="lucideWifiOff" size="1em" /> 1865 <span>{{ 'problemReport.offline.message' | translate }}</span> 1866 </div> 1867 } 1868 1869 <!-- Queued Reports Badge --> 1870 @if (queuedCount > 0) { 1871 <div class="queued-banner"> 1872 <ng-icon name="lucideWifi" size="1em" /> 1873 <span>{{ 'problemReport.queue.pending' | translate: { count: queuedCount } }}</span> 1874 </div> 1875 } 1876 1877 <form class="report-form" (ngSubmit)="submit()"> 1878 <!-- Photo Capture --> 1879 <div class="form-section"> 1880 <label class="form-label">{{ 'problemReport.photo.label' | translate }}</label> 1881 <app-photo-capture 1882 [currentPhoto]="photoBase64" 1883 (photoCapture)="onPhotoCapture($event)" 1884 (photoRemove)="onPhotoRemove()" /> 1885 </div> 1886 1887 <!-- Title --> 1888 <div class="form-section"> 1889 <label class="form-label">{{ 'problemReport.title.label' | translate }} *</label> 1890 <ion-textarea 1891 [(ngModel)]="title" 1892 name="title" 1893 [placeholder]="'problemReport.title.placeholder' | translate" 1894 [maxlength]="200" 1895 [rows]="2" 1896 [disabled]="isSubmitting" 1897 [autoGrow]="true" 1898 class="title-textarea"> 1899 </ion-textarea> 1900 <div class="char-count" [class.warning]="titleLength < 5"> 1901 {{ titleLength }}/200 1902 @if (titleLength > 0 && titleLength < 5) { 1903 <span class="min-hint">({{ 'problemReport.title.minChars' | translate }})</span> 1904 } 1905 </div> 1906 </div> 1907 1908 <!-- Category --> 1909 <div class="form-section"> 1910 <label class="form-label">{{ 'problemReport.category.label' | translate }} *</label> 1911 <ion-select 1912 [(ngModel)]="category" 1913 name="category" 1914 interface="action-sheet" 1915 [placeholder]="'problemReport.category.placeholder' | translate" 1916 [disabled]="isSubmitting" 1917 class="category-select"> 1918 @for (cat of categories; track cat.value) { 1919 <ion-select-option [value]="cat.value"> 1920 {{ cat.labelKey | translate }} 1921 </ion-select-option> 1922 } 1923 </ion-select> 1924 </div> 1925 1926 <!-- Severity --> 1927 <div class="form-section"> 1928 <label class="form-label">{{ 'problemReport.severity.label' | translate }} *</label> 1929 <ion-select 1930 [(ngModel)]="severity" 1931 name="severity" 1932 interface="action-sheet" 1933 [placeholder]="'problemReport.severity.placeholder' | translate" 1934 [disabled]="isSubmitting" 1935 class="severity-select"> 1936 @for (sev of severities; track sev.value) { 1937 <ion-select-option [value]="sev.value"> 1938 {{ sev.labelKey | translate }} 1939 </ion-select-option> 1940 } 1941 </ion-select> 1942 </div> 1943 1944 <!-- Description --> 1945 <div class="form-section"> 1946 <label class="form-label">{{ 'problemReport.description.label' | translate }}</label> 1947 <ion-textarea 1948 [(ngModel)]="description" 1949 name="description" 1950 [placeholder]="'problemReport.description.placeholder' | translate" 1951 [maxlength]="2000" 1952 [rows]="4" 1953 [disabled]="isSubmitting" 1954 [autoGrow]="true" 1955 class="description-textarea"> 1956 </ion-textarea> 1957 <div class="char-count"> 1958 {{ descriptionLength }}/2000 1959 </div> 1960 </div> 1961 1962 <!-- Location Context --> 1963 <div class="form-section location-section"> 1964 <label class="form-label">{{ 'problemReport.location.label' | translate }}</label> 1965 <ion-card class="location-card"> 1966 <ion-card-content> 1967 <div class="location-info"> 1968 @if (locationDescription) { 1969 <div class="location-item"> 1970 <ng-icon name="lucideMapPin" size="1em" />
1971 <span class="location-value">{{ locationDescription }}</span> 1972 </div> 1973 } 1974 @if (campusName) { 1975 <div class="location-item"> 1976 <ng-icon name="lucideBuilding" size="1em" /> 1977 <span class="location-value">{{ campusName }}</span> 1978 </div> 1979 } 1980 @if (!locationDescription && !campusName) { 1981 <div class="location-item unknown"> 1982 <ng-icon name="lucideCircleHelp" size="1em" /> 1983 <span class="location-value">{{ 'problemReport.location.unknown' | translate }}</span> 1984 </div> 1985 } 1986 </div> 1987 </ion-card-content> 1988 </ion-card> 1989 </div> 1990 1991 <!-- Submit Button --> 1992 <div class="submit-section"> 1993 <ion-button 1994 type="submit" 1995 expand="block" 1996 [disabled]="!isFormValid || isSubmitting" 1997 class="submit-button"> 1998 @if (isSubmitting) { 1999 <ion-spinner name="crescent" slot="start" /> 2000 <span>{{ 'problemReport.button.submitting' | translate }}</span> 2001 } @else if (!isOnline) { 2002 <ng-icon name="lucideWifiOff" slot="start" /> 2003 <span>{{ 'problemReport.button.queue' | translate }}</span> 2004 } @else { 2005 <ng-icon name="lucideSend" slot="start" /> 2006 <span>{{ 'problemReport.button.submit' | translate }}</span> 2007 } 2008 </ion-button> 2009 </div> 2010 </form> 2011 </div> 2012</ion-content> 2013`, styles: ["/* src/app/pages/problem-report/problem-report.page.scss */\nion-content {\n --padding-start: 16px;\n --padding-end: 16px;\n --padding-top: 16px;\n --padding-bottom: 16px;\n}\nion-content::part(scroll) {\n overflow-x: hidden;\n}\n.content-container {\n max-width: 600px;\n margin: 0 auto;\n padding: 1rem;\n}\n.report-form {\n background: var(--ion-item-background);\n border-radius: 12px;\n padding: 1.5rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n.report-form .form-section:not(:last-child) {\n margin-bottom: 1.5rem;\n}\n.offline-banner,\n.queued-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n border-radius: 8px;\n margin-bottom: 16px;\n font-size: 14px;\n font-weight: 500;\n}\n.offline-banner ng-icon,\n.queued-banner ng-icon {\n flex-shrink: 0;\n}\n.offline-banner {\n background: var(--ion-color-warning);\n color: var(--ion-color-warning-contrast);\n}\n.queued-banner {\n background: var(--ion-color-info);\n color: var(--ion-color-info-contrast);\n}\n.report-form {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n.form-section {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.form-section .form-label {\n font-size: 14px;\n font-weight: 600;\n color: var(--ion-color-medium);\n}\n.form-section ion-textarea {\n --padding-top: 12px;\n --padding-bottom: 12px;\n --padding-start: 12px;\n --padding-end: 12px;\n --border-radius: 8px;\n --background: var(--ion-color-light);\n --border-color: var(--ion-color-step-150);\n border: 1px solid var(--ion-color-step-200);\n}\n.form-section ion-textarea.title-textarea {\n min-height: 60px;\n}\n.form-section ion-textarea.description-textarea {\n min-height: 100px;\n}\n.form-section ion-select {\n --padding-start: 12px;\n --padding-end: 12px;\n --min-height: 44px;\n --border-radius: 8px;\n --background: var(--ion-color-light);\n --border-color: var(--ion-color-step-200);\n border: 1px solid var(--ion-color-step-200);\n}\n.form-section ion-select.category-select,\n.form-section ion-select.severity-select {\n min-height: 44px;\n}\n.form-section .char-count {\n font-size: 12px;\n color: var(--ion-color-medium);\n text-align: right;\n}\n.form-section .char-count.warning {\n color: var(--ion-color-warning);\n}\n.form-section .char-count .min-hint {\n color: var(--ion-color-danger);\n}\n.location-section .location-card {\n --background: var(--ion-color-light);\n --border-radius: 8px;\n border: 1px solid var(--ion-color-step-200);\n}\n.location-section .location-card ion-card-content {\n padding: 12px !important;\n}\n.location-section .location-card .location-info {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.location-section .location-card .location-info .location-item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n}\n.location-section .location-card .location-info .location-item ng-icon {\n flex-shrink: 0;\n color: var(--ion-color-medium);\n}\n.location-section .location-card .location-info .location-item .location-value {\n color: var(--ion-color-dark);\n font-weight: 500;\n}\n.location-section .location-card .location-info .location-item.gps-location .location-value {\n font-family: monospace;\n font-size: 13px;\n}\n.location-section .location-card .location-info .location-item.unknown .location-value {\n color: var(--ion-color-medium);\n font-style: italic;\n}\n.submit-section {\n margin-top: 8px;\n margin-bottom: 16px;\n}\n.submit-section .submit-button {\n --border-radius: 8px;\n --height: 48px;\n font-weight: 600;\n text-transform: none;\n}\n.submit-section .submit-button ion-spinner {\n color: currentColor;\n}\n"] }] 2014 }], () => [{ type: Router }, { type: ApiService }, { type: ProblemReportStateService }, { type: ProblemReportQueueService }, { type: LocationService }, { type: GPSService }, { type: ToastService }, { type: DatabaseService }, { type: TranslateService }], null); 2015})(); 2016(() => { 2017 (typeof ngDevMode === "undefined" || ngDevMode) && \u0275setClassDebugInfo(ProblemReportPage, { className: "ProblemReportPage", filePath: "src/app/pages/problem-report/problem-report.page.ts", lineNumber: 54 }); 2018})(); 2019export { 2020 ProblemReportPage 2021};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.