| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721 |
- //
- // DetailAplikasiVC.swift
- // Sirani
- //
- // Created by Antigravity on 08/09/26.
- // Copyright © 2026 PT. Mitra Sinerji Teknoindo. All rights reserved.
- //
- import UIKit
- class DetailAplikasiVC: UIViewController {
-
- // MARK: - Data
- var aplikasiData: Aplikasi?
-
- override var preferredStatusBarStyle: UIStatusBarStyle {
- return .lightContent
- }
-
- // MARK: - UI Components
- private let topStatusBarBackground = UIView()
- private let toolbarView = UIView()
- private let btnBack = UIButton(type: .custom)
- private let titleLabel = UILabel()
- private let subtitleLabel = UILabel()
- private let btnShare = UIButton(type: .custom)
-
- private let scrollView = UIScrollView()
- private let contentView = UIView()
- private let contentStack = UIStackView()
-
- // Toast Alert
- private let toastView = UIView()
- private let toastLabel = UILabel()
-
- // MARK: - Lifecycle
- override func viewDidLoad() {
- super.viewDidLoad()
- setupUI()
- populateData()
- }
-
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- AppUtility.lockOrientation(.portrait)
- }
-
- override func viewWillDisappear(_ animated: Bool) {
- super.viewWillDisappear(animated)
- AppUtility.lockOrientation(.all)
- }
-
- // MARK: - UI Setup
- private func setupUI() {
- view.backgroundColor = UIColor(red: 247/255, green: 249/255, blue: 252/255, alpha: 1.0)
-
- // 1. Top Status Bar & Toolbar
- topStatusBarBackground.translatesAutoresizingMaskIntoConstraints = false
- topStatusBarBackground.backgroundColor = UIColor.siraniPrimaryBlue
- view.addSubview(topStatusBarBackground)
-
- toolbarView.translatesAutoresizingMaskIntoConstraints = false
- toolbarView.backgroundColor = UIColor.siraniPrimaryBlue
- view.addSubview(toolbarView)
-
- // Back Button
- btnBack.translatesAutoresizingMaskIntoConstraints = false
- if let backImg = UIImage(named: "toolbar-back") {
- btnBack.setImage(backImg, for: .normal)
- } else {
- btnBack.setImage(UIImage(systemName: "chevron.backward"), for: .normal)
- btnBack.tintColor = .white
- }
- btnBack.addTarget(self, action: #selector(onBackTap), for: .touchUpInside)
- toolbarView.addSubview(btnBack)
-
- // Titles Stack
- let titleStack = UIStackView()
- titleStack.translatesAutoresizingMaskIntoConstraints = false
- titleStack.axis = .vertical
- titleStack.spacing = 2
- titleStack.alignment = .leading
- toolbarView.addSubview(titleStack)
-
- titleLabel.font = UIFont.systemFont(ofSize: 17, weight: .bold)
- titleLabel.textColor = .white
- titleLabel.text = "Detail Aplikasi"
- titleStack.addArrangedSubview(titleLabel)
-
- subtitleLabel.font = UIFont.systemFont(ofSize: 11, weight: .medium)
- subtitleLabel.textColor = UIColor.white.withAlphaComponent(0.8)
- subtitleLabel.text = "Permohonan Layanan DJID"
- titleStack.addArrangedSubview(subtitleLabel)
-
- // Share Button
- btnShare.translatesAutoresizingMaskIntoConstraints = false
- btnShare.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
- btnShare.tintColor = .white
- btnShare.addTarget(self, action: #selector(onShareTap), for: .touchUpInside)
- toolbarView.addSubview(btnShare)
-
- // 2. ScrollView & ContentView
- scrollView.translatesAutoresizingMaskIntoConstraints = false
- scrollView.showsVerticalScrollIndicator = true
- scrollView.alwaysBounceVertical = true
- view.addSubview(scrollView)
-
- contentView.translatesAutoresizingMaskIntoConstraints = false
- scrollView.addSubview(contentView)
-
- contentStack.translatesAutoresizingMaskIntoConstraints = false
- contentStack.axis = .vertical
- contentStack.spacing = 16
- contentStack.alignment = .fill
- contentStack.distribution = .fill
- contentView.addSubview(contentStack)
-
- // 3. Toast Feedback View
- toastView.translatesAutoresizingMaskIntoConstraints = false
- toastView.backgroundColor = UIColor(red: 26/255, green: 29/255, blue: 38/255, alpha: 0.9)
- toastView.layer.cornerRadius = 10
- toastView.layer.masksToBounds = true
- toastView.alpha = 0
- view.addSubview(toastView)
-
- toastLabel.translatesAutoresizingMaskIntoConstraints = false
- toastLabel.font = UIFont.systemFont(ofSize: 12, weight: .semibold)
- toastLabel.textColor = .white
- toastLabel.textAlignment = .center
- toastView.addSubview(toastLabel)
-
- // Layout Constraints
- NSLayoutConstraint.activate([
- // Status Bar Background
- topStatusBarBackground.topAnchor.constraint(equalTo: view.topAnchor),
- topStatusBarBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- topStatusBarBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- topStatusBarBackground.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
-
- // Toolbar
- toolbarView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
- toolbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- toolbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- toolbarView.heightAnchor.constraint(equalToConstant: 56),
-
- btnBack.leadingAnchor.constraint(equalTo: toolbarView.leadingAnchor, constant: 16),
- btnBack.centerYAnchor.constraint(equalTo: toolbarView.centerYAnchor),
- btnBack.widthAnchor.constraint(equalToConstant: 24),
- btnBack.heightAnchor.constraint(equalToConstant: 24),
-
- titleStack.leadingAnchor.constraint(equalTo: btnBack.trailingAnchor, constant: 16),
- titleStack.trailingAnchor.constraint(equalTo: btnShare.leadingAnchor, constant: -12),
- titleStack.centerYAnchor.constraint(equalTo: toolbarView.centerYAnchor),
-
- btnShare.trailingAnchor.constraint(equalTo: toolbarView.trailingAnchor, constant: -16),
- btnShare.centerYAnchor.constraint(equalTo: toolbarView.centerYAnchor),
- btnShare.widthAnchor.constraint(equalToConstant: 28),
- btnShare.heightAnchor.constraint(equalToConstant: 28),
-
- // ScrollView
- scrollView.topAnchor.constraint(equalTo: toolbarView.bottomAnchor),
- scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
-
- contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
- contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
- contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
- contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
- contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
-
- contentStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
- contentStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
- contentStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
- contentStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -24),
-
- // Toast
- toastView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
- toastView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
- toastLabel.topAnchor.constraint(equalTo: toastView.topAnchor, constant: 10),
- toastLabel.bottomAnchor.constraint(equalTo: toastView.bottomAnchor, constant: -10),
- toastLabel.leadingAnchor.constraint(equalTo: toastView.leadingAnchor, constant: 16),
- toastLabel.trailingAnchor.constraint(equalTo: toastView.trailingAnchor, constant: -16)
- ])
- }
-
- // MARK: - Populate Data
- private func populateData() {
- guard let data = aplikasiData else { return }
-
- subtitleLabel.text = "APL ID: \(data.apl_id)"
-
- // 1. Hero Card: Status & Identitas Aplikasi
- contentStack.addArrangedSubview(createHeroCard(data: data))
-
- // 2. Data Pemohon (Perusahaan)
- contentStack.addArrangedSubview(createPemohonCard(data: data))
-
- // 3. Data Perangkat
- contentStack.addArrangedSubview(createPerangkatCard(data: data))
-
- // 4. Disposisi & Status Proses
- contentStack.addArrangedSubview(createDisposisiCard(data: data))
-
- // 5. Close Button
- let btnClose = UIButton(type: .system)
- btnClose.translatesAutoresizingMaskIntoConstraints = false
- btnClose.setTitle("Kembali ke Daftar Aplikasi", for: .normal)
- btnClose.setTitleColor(.white, for: .normal)
- btnClose.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .bold)
- btnClose.backgroundColor = UIColor.siraniPrimaryBlue
- btnClose.layer.cornerRadius = 14
- btnClose.layer.shadowColor = UIColor.siraniPrimaryBlue.cgColor
- btnClose.layer.shadowOffset = CGSize(width: 0, height: 4)
- btnClose.layer.shadowRadius = 8
- btnClose.layer.shadowOpacity = 0.25
- btnClose.heightAnchor.constraint(equalToConstant: 48).isActive = true
- btnClose.addTarget(self, action: #selector(onBackTap), for: .touchUpInside)
- contentStack.addArrangedSubview(btnClose)
- }
-
- // MARK: - Card Builders
-
- // 1. Hero Card (APL ID, ID Izin, Status, Resi)
- private func createHeroCard(data: Aplikasi) -> UIView {
- let card = createCardContainer()
- let stack = UIStackView()
- stack.translatesAutoresizingMaskIntoConstraints = false
- stack.axis = .vertical
- stack.spacing = 14
- card.addSubview(stack)
-
- // Header Row: Badge Icon + APL ID + Status Pill
- let headerRow = UIStackView()
- headerRow.axis = .horizontal
- headerRow.alignment = .center
- headerRow.spacing = 10
-
- let iconBadge = UIView()
- iconBadge.translatesAutoresizingMaskIntoConstraints = false
- iconBadge.backgroundColor = UIColor.siraniSoftBlue
- iconBadge.layer.cornerRadius = 10
- iconBadge.layer.masksToBounds = true
- let icon = UIImageView(image: UIImage(systemName: "app.badge.fill"))
- icon.tintColor = UIColor.siraniPrimaryBlue
- icon.contentMode = .scaleAspectFit
- icon.translatesAutoresizingMaskIntoConstraints = false
- iconBadge.addSubview(icon)
- NSLayoutConstraint.activate([
- iconBadge.widthAnchor.constraint(equalToConstant: 38),
- iconBadge.heightAnchor.constraint(equalToConstant: 38),
- icon.centerXAnchor.constraint(equalTo: iconBadge.centerXAnchor),
- icon.centerYAnchor.constraint(equalTo: iconBadge.centerYAnchor),
- icon.widthAnchor.constraint(equalToConstant: 20),
- icon.heightAnchor.constraint(equalToConstant: 20)
- ])
- headerRow.addArrangedSubview(iconBadge)
-
- let idStack = UIStackView()
- idStack.axis = .vertical
- idStack.spacing = 2
-
- let aplTitle = UILabel()
- aplTitle.font = UIFont.systemFont(ofSize: 10, weight: .bold)
- aplTitle.textColor = UIColor(red: 140/255, green: 155/255, blue: 175/255, alpha: 1.0)
- aplTitle.text = "APL ID"
- idStack.addArrangedSubview(aplTitle)
-
- let aplVal = UILabel()
- aplVal.font = UIFont.systemFont(ofSize: 18, weight: .bold)
- aplVal.textColor = UIColor.siraniPrimaryBlue
- aplVal.text = "\(data.apl_id)"
- idStack.addArrangedSubview(aplVal)
- headerRow.addArrangedSubview(idStack)
-
- let spacer = UIView()
- spacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
- headerRow.addArrangedSubview(spacer)
-
- // Status Pill
- let statusPill = UIView()
- statusPill.translatesAutoresizingMaskIntoConstraints = false
- statusPill.layer.cornerRadius = 8
- statusPill.layer.masksToBounds = true
-
- let statusStr = data.StatusStr.trimmingCharacters(in: .whitespacesAndNewlines)
- let statusColorHex = data.StatusBgColor.trimmingCharacters(in: .whitespacesAndNewlines)
- let statusLabel = UILabel()
- statusLabel.translatesAutoresizingMaskIntoConstraints = false
- statusLabel.font = UIFont.systemFont(ofSize: 11, weight: .bold)
- statusLabel.text = statusStr.isEmpty ? "PERMOHONAN" : statusStr
-
- if let customColor = UIColor(hexString: statusColorHex), !statusColorHex.isEmpty {
- statusPill.backgroundColor = customColor.withAlphaComponent(0.15)
- statusLabel.textColor = customColor
- } else {
- statusPill.backgroundColor = UIColor(red: 230/255, green: 244/255, blue: 234/255, alpha: 1.0)
- statusLabel.textColor = UIColor(red: 19/255, green: 115/255, blue: 51/255, alpha: 1.0)
- }
- statusPill.addSubview(statusLabel)
- NSLayoutConstraint.activate([
- statusLabel.topAnchor.constraint(equalTo: statusPill.topAnchor, constant: 6),
- statusLabel.bottomAnchor.constraint(equalTo: statusPill.bottomAnchor, constant: -6),
- statusLabel.leadingAnchor.constraint(equalTo: statusPill.leadingAnchor, constant: 10),
- statusLabel.trailingAnchor.constraint(equalTo: statusPill.trailingAnchor, constant: -10)
- ])
- headerRow.addArrangedSubview(statusPill)
- stack.addArrangedSubview(headerRow)
-
- stack.addArrangedSubview(createDivider())
-
- // ID Izin Box with Copy Action
- let idIzinBox = UIView()
- idIzinBox.backgroundColor = UIColor(red: 248/255, green: 250/255, blue: 254/255, alpha: 1.0)
- idIzinBox.layer.cornerRadius = 10
- idIzinBox.layer.borderWidth = 1
- idIzinBox.layer.borderColor = UIColor(red: 230/255, green: 237/255, blue: 248/255, alpha: 1.0).cgColor
-
- let idIzinStack = UIStackView()
- idIzinStack.translatesAutoresizingMaskIntoConstraints = false
- idIzinStack.axis = .horizontal
- idIzinStack.alignment = .center
- idIzinStack.spacing = 10
- idIzinBox.addSubview(idIzinStack)
-
- let idIzinInner = UIStackView()
- idIzinInner.axis = .vertical
- idIzinInner.spacing = 2
-
- let idIzinTitle = UILabel()
- idIzinTitle.font = UIFont.systemFont(ofSize: 10, weight: .bold)
- idIzinTitle.textColor = UIColor(red: 140/255, green: 155/255, blue: 175/255, alpha: 1.0)
- idIzinTitle.text = "ID IZIN PERMOHONAN"
- idIzinInner.addArrangedSubview(idIzinTitle)
-
- let idIzinVal = UILabel()
- idIzinVal.font = UIFont.systemFont(ofSize: 13, weight: .bold)
- idIzinVal.textColor = UIColor(red: 30/255, green: 40/255, blue: 60/255, alpha: 1.0)
- idIzinVal.text = data.id_izin.isEmpty ? "-" : data.id_izin
- idIzinVal.numberOfLines = 1
- idIzinInner.addArrangedSubview(idIzinVal)
- idIzinStack.addArrangedSubview(idIzinInner)
-
- let copySpacer = UIView()
- copySpacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
- idIzinStack.addArrangedSubview(copySpacer)
-
- // Copy Button
- let btnCopy = UIButton(type: .system)
- btnCopy.setImage(UIImage(systemName: "doc.on.doc"), for: .normal)
- btnCopy.setTitle(" Salin", for: .normal)
- btnCopy.titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .bold)
- btnCopy.tintColor = UIColor.siraniPrimaryBlue
- btnCopy.backgroundColor = UIColor.white
- btnCopy.layer.cornerRadius = 8
- btnCopy.layer.borderWidth = 1
- btnCopy.layer.borderColor = UIColor(red: 220/255, green: 230/255, blue: 245/255, alpha: 1.0).cgColor
- btnCopy.contentEdgeInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
- btnCopy.addTarget(self, action: #selector(onCopyIdIzin), for: .touchUpInside)
- idIzinStack.addArrangedSubview(btnCopy)
-
- NSLayoutConstraint.activate([
- idIzinStack.topAnchor.constraint(equalTo: idIzinBox.topAnchor, constant: 10),
- idIzinStack.bottomAnchor.constraint(equalTo: idIzinBox.bottomAnchor, constant: -10),
- idIzinStack.leadingAnchor.constraint(equalTo: idIzinBox.leadingAnchor, constant: 12),
- idIzinStack.trailingAnchor.constraint(equalTo: idIzinBox.trailingAnchor, constant: -12)
- ])
- stack.addArrangedSubview(idIzinBox)
-
- // 2-Column Resi Box
- let resiStack = UIStackView()
- resiStack.axis = .horizontal
- resiStack.distribution = .fillEqually
- resiStack.spacing = 10
-
- let resiNoBox = createMiniInfoBox(title: "NO. RESI", value: data.resi_no.isEmpty ? "-" : data.resi_no)
- let resiDateFormatted = formatDateString(data.resi_date)
- let resiDateBox = createMiniInfoBox(title: "TANGGAL RESI", value: resiDateFormatted)
- resiStack.addArrangedSubview(resiNoBox)
- resiStack.addArrangedSubview(resiDateBox)
- stack.addArrangedSubview(resiStack)
-
- NSLayoutConstraint.activate([
- stack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16),
- stack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16),
- stack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
- stack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16)
- ])
-
- return card
- }
-
- // 2. Data Pemohon Card
- private func createPemohonCard(data: Aplikasi) -> UIView {
- let card = createCardContainer()
- let stack = UIStackView()
- stack.translatesAutoresizingMaskIntoConstraints = false
- stack.axis = .vertical
- stack.spacing = 12
- card.addSubview(stack)
-
- // Section Header
- stack.addArrangedSubview(createSectionHeader(title: "DATA PEMOHON", iconName: "building.2.fill"))
- stack.addArrangedSubview(createDivider())
-
- // Company Name
- let compName = UILabel()
- compName.font = UIFont.systemFont(ofSize: 15, weight: .bold)
- compName.textColor = UIColor(red: 26/255, green: 29/255, blue: 38/255, alpha: 1.0)
- compName.numberOfLines = 0
- compName.text = data.cust_name.isEmpty ? "-" : data.cust_name
- stack.addArrangedSubview(compName)
-
- // Details Box
- let box = UIView()
- box.backgroundColor = UIColor(red: 248/255, green: 250/255, blue: 253/255, alpha: 1.0)
- box.layer.cornerRadius = 10
- box.layer.borderWidth = 1
- box.layer.borderColor = UIColor(red: 235/255, green: 240/255, blue: 248/255, alpha: 1.0).cgColor
-
- let boxStack = UIStackView()
- boxStack.translatesAutoresizingMaskIntoConstraints = false
- boxStack.axis = .vertical
- boxStack.spacing = 10
- box.addSubview(boxStack)
-
- boxStack.addArrangedSubview(createKeyValueRow(key: "NIB (Nomor Induk Berusaha)", value: data.nib.isEmpty ? "-" : data.nib))
- boxStack.addArrangedSubview(createKeyValueRow(key: "ID Pelanggan (Cust ID)", value: "\(data.cust_id)"))
- boxStack.addArrangedSubview(createKeyValueRow(key: "ID Proyek", value: data.id_proyek.isEmpty ? "-" : data.id_proyek))
-
- NSLayoutConstraint.activate([
- boxStack.topAnchor.constraint(equalTo: box.topAnchor, constant: 12),
- boxStack.bottomAnchor.constraint(equalTo: box.bottomAnchor, constant: -12),
- boxStack.leadingAnchor.constraint(equalTo: box.leadingAnchor, constant: 12),
- boxStack.trailingAnchor.constraint(equalTo: box.trailingAnchor, constant: -12)
- ])
- stack.addArrangedSubview(box)
-
- NSLayoutConstraint.activate([
- stack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16),
- stack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16),
- stack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
- stack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16)
- ])
-
- return card
- }
-
- // 3. Data Perangkat Card
- private func createPerangkatCard(data: Aplikasi) -> UIView {
- let card = createCardContainer()
- let stack = UIStackView()
- stack.translatesAutoresizingMaskIntoConstraints = false
- stack.axis = .vertical
- stack.spacing = 12
- card.addSubview(stack)
-
- // Section Header
- stack.addArrangedSubview(createSectionHeader(title: "DATA PERANGKAT", iconName: "antenna.radiowaves.left.and.right"))
- stack.addArrangedSubview(createDivider())
-
- // Device Name
- let eqpName = UILabel()
- eqpName.font = UIFont.systemFont(ofSize: 15, weight: .bold)
- eqpName.textColor = UIColor(red: 26/255, green: 29/255, blue: 38/255, alpha: 1.0)
- eqpName.numberOfLines = 0
- eqpName.text = data.eqp_name.isEmpty ? "-" : data.eqp_name
- stack.addArrangedSubview(eqpName)
-
- // Spec 2x2 Grid
- let row1 = UIStackView()
- row1.axis = .horizontal
- row1.distribution = .fillEqually
- row1.spacing = 10
- row1.addArrangedSubview(createMiniInfoBox(title: "MERK", value: data.merk.isEmpty ? "-" : data.merk))
- row1.addArrangedSubview(createMiniInfoBox(title: "MODEL", value: data.model.isEmpty ? "-" : data.model))
- stack.addArrangedSubview(row1)
-
- let row2 = UIStackView()
- row2.axis = .horizontal
- row2.distribution = .fillEqually
- row2.spacing = 10
- row2.addArrangedSubview(createMiniInfoBox(title: "BRAND", value: data.brand.isEmpty ? "-" : data.brand))
- row2.addArrangedSubview(createMiniInfoBox(title: "JENIS PERMOHONAN", value: "Sertifikasi DJID"))
- stack.addArrangedSubview(row2)
-
- NSLayoutConstraint.activate([
- stack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16),
- stack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16),
- stack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
- stack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16)
- ])
-
- return card
- }
-
- // 4. Disposisi & Status Card
- private func createDisposisiCard(data: Aplikasi) -> UIView {
- let card = createCardContainer()
- let stack = UIStackView()
- stack.translatesAutoresizingMaskIntoConstraints = false
- stack.axis = .vertical
- stack.spacing = 12
- card.addSubview(stack)
-
- stack.addArrangedSubview(createSectionHeader(title: "DISPOSISI & STATUS PROSES", iconName: "arrow.triangle.branch"))
- stack.addArrangedSubview(createDivider())
-
- let box = UIView()
- box.backgroundColor = UIColor(red: 248/255, green: 250/255, blue: 253/255, alpha: 1.0)
- box.layer.cornerRadius = 10
- box.layer.borderWidth = 1
- box.layer.borderColor = UIColor(red: 235/255, green: 240/255, blue: 248/255, alpha: 1.0).cgColor
-
- let boxStack = UIStackView()
- boxStack.translatesAutoresizingMaskIntoConstraints = false
- boxStack.axis = .vertical
- boxStack.spacing = 10
- box.addSubview(boxStack)
-
- boxStack.addArrangedSubview(createKeyValueRow(key: "Petugas Disposisi", value: data.disposisi.isEmpty ? "-" : data.disposisi))
- boxStack.addArrangedSubview(createKeyValueRow(key: "Status Saat Ini", value: data.StatusStr.isEmpty ? "-" : data.StatusStr))
- boxStack.addArrangedSubview(createKeyValueRow(key: "Kode Status", value: data.status.isEmpty ? "-" : data.status))
- boxStack.addArrangedSubview(createKeyValueRow(key: "ID Permohonan Sistem", value: data.id.isEmpty ? "-" : data.id))
-
- NSLayoutConstraint.activate([
- boxStack.topAnchor.constraint(equalTo: box.topAnchor, constant: 12),
- boxStack.bottomAnchor.constraint(equalTo: box.bottomAnchor, constant: -12),
- boxStack.leadingAnchor.constraint(equalTo: box.leadingAnchor, constant: 12),
- boxStack.trailingAnchor.constraint(equalTo: box.trailingAnchor, constant: -12)
- ])
- stack.addArrangedSubview(box)
-
- NSLayoutConstraint.activate([
- stack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16),
- stack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16),
- stack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
- stack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16)
- ])
-
- return card
- }
-
- // MARK: - Reusable UI Helpers
- private func createCardContainer() -> UIView {
- let card = UIView()
- card.backgroundColor = .white
- card.layer.cornerRadius = 16
- card.layer.borderWidth = 1
- card.layer.borderColor = UIColor(red: 228/255, green: 235/255, blue: 245/255, alpha: 1.0).cgColor
- card.layer.shadowColor = UIColor.black.cgColor
- card.layer.shadowOffset = CGSize(width: 0, height: 2)
- card.layer.shadowRadius = 8
- card.layer.shadowOpacity = 0.05
- return card
- }
-
- private func createDivider() -> UIView {
- let div = UIView()
- div.translatesAutoresizingMaskIntoConstraints = false
- div.backgroundColor = UIColor(red: 240/255, green: 244/255, blue: 250/255, alpha: 1.0)
- div.heightAnchor.constraint(equalToConstant: 1).isActive = true
- return div
- }
-
- private func createSectionHeader(title: String, iconName: String) -> UIView {
- let stack = UIStackView()
- stack.axis = .horizontal
- stack.spacing = 8
- stack.alignment = .center
-
- let icon = UIImageView(image: UIImage(systemName: iconName))
- icon.tintColor = UIColor.siraniPrimaryBlue
- icon.contentMode = .scaleAspectFit
- icon.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- icon.widthAnchor.constraint(equalToConstant: 16),
- icon.heightAnchor.constraint(equalToConstant: 16)
- ])
- stack.addArrangedSubview(icon)
-
- let lbl = UILabel()
- lbl.font = UIFont.systemFont(ofSize: 12, weight: .bold)
- lbl.textColor = UIColor.siraniPrimaryBlue
- lbl.text = title
- stack.addArrangedSubview(lbl)
-
- return stack
- }
-
- private func createMiniInfoBox(title: String, value: String) -> UIView {
- let box = UIView()
- box.backgroundColor = UIColor(red: 248/255, green: 250/255, blue: 253/255, alpha: 1.0)
- box.layer.cornerRadius = 10
- box.layer.borderWidth = 1
- box.layer.borderColor = UIColor(red: 235/255, green: 240/255, blue: 248/255, alpha: 1.0).cgColor
-
- let stack = UIStackView()
- stack.translatesAutoresizingMaskIntoConstraints = false
- stack.axis = .vertical
- stack.spacing = 3
- box.addSubview(stack)
-
- let lblTitle = UILabel()
- lblTitle.font = UIFont.systemFont(ofSize: 9, weight: .bold)
- lblTitle.textColor = UIColor(red: 140/255, green: 155/255, blue: 175/255, alpha: 1.0)
- lblTitle.text = title
- stack.addArrangedSubview(lblTitle)
-
- let lblVal = UILabel()
- lblVal.font = UIFont.systemFont(ofSize: 12, weight: .semibold)
- lblVal.textColor = UIColor(red: 40/255, green: 50/255, blue: 65/255, alpha: 1.0)
- lblVal.numberOfLines = 2
- lblVal.text = value
- stack.addArrangedSubview(lblVal)
-
- NSLayoutConstraint.activate([
- stack.topAnchor.constraint(equalTo: box.topAnchor, constant: 8),
- stack.bottomAnchor.constraint(equalTo: box.bottomAnchor, constant: -8),
- stack.leadingAnchor.constraint(equalTo: box.leadingAnchor, constant: 10),
- stack.trailingAnchor.constraint(equalTo: box.trailingAnchor, constant: -10)
- ])
-
- return box
- }
-
- private func createKeyValueRow(key: String, value: String) -> UIView {
- let stack = UIStackView()
- stack.axis = .vertical
- stack.spacing = 2
-
- let k = UILabel()
- k.font = UIFont.systemFont(ofSize: 10, weight: .bold)
- k.textColor = UIColor(red: 140/255, green: 155/255, blue: 175/255, alpha: 1.0)
- k.text = key
- stack.addArrangedSubview(k)
-
- let v = UILabel()
- v.font = UIFont.systemFont(ofSize: 13, weight: .semibold)
- v.textColor = UIColor(red: 35/255, green: 45/255, blue: 60/255, alpha: 1.0)
- v.numberOfLines = 0
- v.text = value
- stack.addArrangedSubview(v)
-
- return stack
- }
-
- private func formatDateString(_ dateStr: String) -> String {
- guard !dateStr.isEmpty, dateStr != "-" else { return "-" }
-
- // Try ISO 8601 parsing first
- let isoFormatter = ISO8601DateFormatter()
- isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
- var date = isoFormatter.date(from: dateStr)
-
- if date == nil {
- isoFormatter.formatOptions = [.withInternetDateTime]
- date = isoFormatter.date(from: dateStr)
- }
-
- if let d = date {
- let displayFormatter = DateFormatter()
- displayFormatter.locale = Locale(identifier: "id_ID")
- displayFormatter.dateFormat = "dd MMM yyyy, HH:mm"
- return displayFormatter.string(from: d) + " WIB"
- }
-
- // Fallback for simple space-separated date strings
- let parts = dateStr.split(separator: "T")
- if let first = parts.first {
- return String(first)
- }
- return dateStr
- }
-
- // MARK: - Actions
- @objc private func onBackTap() {
- dismiss(animated: true, completion: nil)
- }
-
- @objc private func onCopyIdIzin() {
- guard let data = aplikasiData, !data.id_izin.isEmpty else { return }
- UIPasteboard.general.string = data.id_izin
-
- let generator = UIImpactFeedbackGenerator(style: .medium)
- generator.impactOccurred()
-
- showToast(message: "ID Izin berhasil disalin")
- }
-
- @objc private func onShareTap() {
- guard let data = aplikasiData else { return }
- let text = """
- Detail Permohonan Aplikasi SIRANI
- APL ID: \(data.apl_id)
- ID Izin: \(data.id_izin)
- Pemohon: \(data.cust_name)
- Perangkat: \(data.eqp_name)
- Merk/Model: \(data.merk) / \(data.model)
- Status: \(data.StatusStr)
- """
-
- let activityVC = UIActivityViewController(activityItems: [text], applicationActivities: nil)
- if let popover = activityVC.popoverPresentationController {
- popover.sourceView = btnShare
- popover.sourceRect = btnShare.bounds
- }
- present(activityVC, animated: true, completion: nil)
- }
-
- private func showToast(message: String) {
- toastLabel.text = message
- UIView.animate(withDuration: 0.25, animations: {
- self.toastView.alpha = 1.0
- }) { _ in
- UIView.animate(withDuration: 0.25, delay: 1.5, options: .curveEaseOut, animations: {
- self.toastView.alpha = 0
- }, completion: nil)
- }
- }
- }
|