// // AplikasiCell.swift // Sirani // // Created by Hendi on 21/06/23. // Copyright © 2026 PT. Mitra Sinerji Teknoindo. All rights reserved. // import UIKit import MaterialComponents.MaterialCards import SkeletonView protocol AplikasiCellDelegate: NSObjectProtocol { func openAplikasiDetail(data: Aplikasi?) } class AplikasiCell: UITableViewCell { @IBOutlet weak var card: MDCCard! @IBOutlet weak var lblIdIzin: UILabel! @IBOutlet weak var lblAplId: UILabel! @IBOutlet weak var lblResiNo: UILabel! @IBOutlet weak var lblResiDate: UILabel! @IBOutlet weak var lblCustName: UILabel! @IBOutlet weak var lblEqpName: UILabel! @IBOutlet weak var lblMerk: UILabel! @IBOutlet weak var lblModel: UILabel! @IBOutlet weak var lblBrand: UILabel! @IBOutlet weak var lblDisposisi: UILabel! @IBOutlet weak var lblStatusStr: UILabel! @IBOutlet weak var statusTop: NSLayoutConstraint! @IBOutlet weak var disposisiTitle: UILabel! var cellDelegate: AplikasiCellDelegate? var dataAplikasi: Aplikasi? // Modern Programmatic Subviews private let modernContainer = UIView() private let modernBadgeView = UIView() private let modernBadgeIcon = UIImageView() private let modernAplIdLabel = UILabel() private let modernIdIzinLabel = UILabel() private let modernCategoryLabel = UILabel() private let modernStatusPill = UIView() private let modernStatusLabel = UILabel() private let modernEqpLabel = UILabel() private let modernCompanyIcon = UIImageView() private let modernCustLabel = UILabel() private let modernSpecStack = UIStackView() private let modernMerkCard = UIView() private let modernMerkTitle = UILabel() private let modernMerkVal = UILabel() private let modernResiCard = UIView() private let modernResiTitle = UILabel() private let modernResiVal = UILabel() private let modernDisposisiCard = UIView() private let modernDisposisiIcon = UIImageView() private let modernDisposisiLabel = UILabel() private let modernDetailLabel = UILabel() private let modernChevronIcon = UIImageView() private var isModernLayoutBuilt = false override func awakeFromNib() { super.awakeFromNib() self.selectionStyle = .none self.backgroundColor = .clear setupCardVisuals() setupModernLayout() } private func setupCardVisuals() { guard let card = card else { return } card.cornerRadius = 16 card.setShadowElevation(ShadowElevation(rawValue: 2), for: .normal) card.setShadowColor(UIColor.black.withAlphaComponent(0.06), for: .normal) card.layer.borderColor = UIColor(red: 228/255, green: 235/255, blue: 245/255, alpha: 1.0).cgColor card.layer.borderWidth = 1.0 card.backgroundColor = .white } private func setupModernLayout() { guard let card = card, !isModernLayoutBuilt else { return } isModernLayoutBuilt = true for subview in card.subviews { subview.isHidden = true } modernContainer.translatesAutoresizingMaskIntoConstraints = false modernContainer.backgroundColor = .clear card.addSubview(modernContainer) NSLayoutConstraint.activate([ modernContainer.topAnchor.constraint(equalTo: card.topAnchor, constant: 14), modernContainer.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -14), modernContainer.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 14), modernContainer.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -14) ]) // 1. Header Bar let headerStack = UIStackView() headerStack.translatesAutoresizingMaskIntoConstraints = false headerStack.axis = .horizontal headerStack.alignment = .center headerStack.spacing = 10 modernContainer.addSubview(headerStack) modernBadgeView.translatesAutoresizingMaskIntoConstraints = false modernBadgeView.backgroundColor = UIColor(red: 235/255, green: 242/255, blue: 252/255, alpha: 1.0) modernBadgeView.layer.cornerRadius = 8 modernBadgeView.layer.masksToBounds = true modernBadgeIcon.translatesAutoresizingMaskIntoConstraints = false if #available(iOS 13.0, *) { modernBadgeIcon.image = UIImage(systemName: "app.badge.fill") } modernBadgeIcon.tintColor = UIColor.siraniPrimaryBlue modernBadgeIcon.contentMode = .scaleAspectFit modernBadgeView.addSubview(modernBadgeIcon) NSLayoutConstraint.activate([ modernBadgeView.widthAnchor.constraint(equalToConstant: 32), modernBadgeView.heightAnchor.constraint(equalToConstant: 32), modernBadgeIcon.centerXAnchor.constraint(equalTo: modernBadgeView.centerXAnchor), modernBadgeIcon.centerYAnchor.constraint(equalTo: modernBadgeView.centerYAnchor), modernBadgeIcon.widthAnchor.constraint(equalToConstant: 18), modernBadgeIcon.heightAnchor.constraint(equalToConstant: 18) ]) headerStack.addArrangedSubview(modernBadgeView) let idStack = UIStackView() idStack.axis = .vertical idStack.spacing = 2 modernAplIdLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold) modernAplIdLabel.textColor = UIColor.siraniPrimaryBlue modernAplIdLabel.numberOfLines = 1 modernAplIdLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) modernAplIdLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) idStack.addArrangedSubview(modernAplIdLabel) modernIdIzinLabel.font = UIFont.systemFont(ofSize: 11, weight: .semibold) modernIdIzinLabel.textColor = UIColor(red: 113/255, green: 128/255, blue: 150/255, alpha: 1.0) modernIdIzinLabel.numberOfLines = 1 modernIdIzinLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) modernIdIzinLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) idStack.addArrangedSubview(modernIdIzinLabel) headerStack.addArrangedSubview(idStack) // Status Pill modernStatusPill.translatesAutoresizingMaskIntoConstraints = false modernStatusPill.layer.cornerRadius = 6 modernStatusPill.layer.masksToBounds = true modernStatusPill.backgroundColor = UIColor(red: 230/255, green: 244/255, blue: 234/255, alpha: 1.0) modernStatusLabel.translatesAutoresizingMaskIntoConstraints = false modernStatusLabel.font = UIFont.systemFont(ofSize: 10, weight: .bold) modernStatusLabel.textColor = UIColor(red: 19/255, green: 115/255, blue: 51/255, alpha: 1.0) modernStatusLabel.textAlignment = .center modernStatusPill.addSubview(modernStatusLabel) NSLayoutConstraint.activate([ modernStatusLabel.topAnchor.constraint(equalTo: modernStatusPill.topAnchor, constant: 4), modernStatusLabel.bottomAnchor.constraint(equalTo: modernStatusPill.bottomAnchor, constant: -4), modernStatusLabel.leadingAnchor.constraint(equalTo: modernStatusPill.leadingAnchor, constant: 8), modernStatusLabel.trailingAnchor.constraint(equalTo: modernStatusPill.trailingAnchor, constant: -8) ]) headerStack.addArrangedSubview(modernStatusPill) // 2. Divider let divider = UIView() divider.translatesAutoresizingMaskIntoConstraints = false divider.backgroundColor = UIColor(red: 240/255, green: 244/255, blue: 250/255, alpha: 1.0) modernContainer.addSubview(divider) // 3. Equipment Name modernEqpLabel.translatesAutoresizingMaskIntoConstraints = false modernEqpLabel.font = UIFont.systemFont(ofSize: 15, weight: .bold) modernEqpLabel.textColor = UIColor(red: 26/255, green: 29/255, blue: 38/255, alpha: 1.0) modernEqpLabel.numberOfLines = 2 modernContainer.addSubview(modernEqpLabel) // 4. Company Row let companyStack = UIStackView() companyStack.translatesAutoresizingMaskIntoConstraints = false companyStack.axis = .horizontal companyStack.spacing = 6 companyStack.alignment = .center if #available(iOS 13.0, *) { modernCompanyIcon.image = UIImage(systemName: "building.2.fill") } modernCompanyIcon.tintColor = UIColor(red: 120/255, green: 135/255, blue: 155/255, alpha: 1.0) modernCompanyIcon.contentMode = .scaleAspectFit modernCompanyIcon.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ modernCompanyIcon.widthAnchor.constraint(equalToConstant: 14), modernCompanyIcon.heightAnchor.constraint(equalToConstant: 14) ]) companyStack.addArrangedSubview(modernCompanyIcon) modernCustLabel.font = UIFont.systemFont(ofSize: 12, weight: .semibold) modernCustLabel.textColor = UIColor(red: 90/255, green: 105/255, blue: 125/255, alpha: 1.0) modernCustLabel.numberOfLines = 1 companyStack.addArrangedSubview(modernCustLabel) modernContainer.addSubview(companyStack) // 5. Spec Mini Cards modernSpecStack.translatesAutoresizingMaskIntoConstraints = false modernSpecStack.axis = .horizontal modernSpecStack.distribution = .fillEqually modernSpecStack.spacing = 8 modernContainer.addSubview(modernSpecStack) // Merk Card modernMerkCard.backgroundColor = UIColor(red: 248/255, green: 250/255, blue: 253/255, alpha: 1.0) modernMerkCard.layer.cornerRadius = 8 modernMerkCard.layer.borderWidth = 1 modernMerkCard.layer.borderColor = UIColor(red: 235/255, green: 240/255, blue: 248/255, alpha: 1.0).cgColor let merkInnerStack = UIStackView() merkInnerStack.translatesAutoresizingMaskIntoConstraints = false merkInnerStack.axis = .vertical merkInnerStack.spacing = 2 modernMerkCard.addSubview(merkInnerStack) modernMerkTitle.font = UIFont.systemFont(ofSize: 9, weight: .bold) modernMerkTitle.textColor = UIColor(red: 140/255, green: 155/255, blue: 175/255, alpha: 1.0) modernMerkTitle.text = "MERK & MODEL" merkInnerStack.addArrangedSubview(modernMerkTitle) modernMerkVal.font = UIFont.systemFont(ofSize: 11, weight: .semibold) modernMerkVal.textColor = UIColor(red: 40/255, green: 50/255, blue: 65/255, alpha: 1.0) modernMerkVal.numberOfLines = 1 merkInnerStack.addArrangedSubview(modernMerkVal) NSLayoutConstraint.activate([ merkInnerStack.topAnchor.constraint(equalTo: modernMerkCard.topAnchor, constant: 6), merkInnerStack.bottomAnchor.constraint(equalTo: modernMerkCard.bottomAnchor, constant: -6), merkInnerStack.leadingAnchor.constraint(equalTo: modernMerkCard.leadingAnchor, constant: 8), merkInnerStack.trailingAnchor.constraint(equalTo: modernMerkCard.trailingAnchor, constant: -8) ]) modernSpecStack.addArrangedSubview(modernMerkCard) // Resi Card modernResiCard.backgroundColor = UIColor(red: 248/255, green: 250/255, blue: 253/255, alpha: 1.0) modernResiCard.layer.cornerRadius = 8 modernResiCard.layer.borderWidth = 1 modernResiCard.layer.borderColor = UIColor(red: 235/255, green: 240/255, blue: 248/255, alpha: 1.0).cgColor let resiInnerStack = UIStackView() resiInnerStack.translatesAutoresizingMaskIntoConstraints = false resiInnerStack.axis = .vertical resiInnerStack.spacing = 2 modernResiCard.addSubview(resiInnerStack) modernResiTitle.font = UIFont.systemFont(ofSize: 9, weight: .bold) modernResiTitle.textColor = UIColor(red: 140/255, green: 155/255, blue: 175/255, alpha: 1.0) modernResiTitle.text = "NO. RESI" resiInnerStack.addArrangedSubview(modernResiTitle) modernResiVal.font = UIFont.systemFont(ofSize: 11, weight: .semibold) modernResiVal.textColor = UIColor(red: 40/255, green: 50/255, blue: 65/255, alpha: 1.0) modernResiVal.numberOfLines = 1 resiInnerStack.addArrangedSubview(modernResiVal) NSLayoutConstraint.activate([ resiInnerStack.topAnchor.constraint(equalTo: modernResiCard.topAnchor, constant: 6), resiInnerStack.bottomAnchor.constraint(equalTo: modernResiCard.bottomAnchor, constant: -6), resiInnerStack.leadingAnchor.constraint(equalTo: modernResiCard.leadingAnchor, constant: 8), resiInnerStack.trailingAnchor.constraint(equalTo: modernResiCard.trailingAnchor, constant: -8) ]) modernSpecStack.addArrangedSubview(modernResiCard) // 6. Disposisi / Bottom Footer modernDisposisiCard.translatesAutoresizingMaskIntoConstraints = false modernDisposisiCard.backgroundColor = UIColor(red: 244/255, green: 248/255, blue: 254/255, alpha: 1.0) modernDisposisiCard.layer.cornerRadius = 8 modernContainer.addSubview(modernDisposisiCard) let disposisiStack = UIStackView() disposisiStack.translatesAutoresizingMaskIntoConstraints = false disposisiStack.axis = .horizontal disposisiStack.alignment = .center disposisiStack.spacing = 6 modernDisposisiCard.addSubview(disposisiStack) if #available(iOS 13.0, *) { modernDisposisiIcon.image = UIImage(systemName: "arrow.triangle.branch") } modernDisposisiIcon.tintColor = UIColor.siraniPrimaryBlue modernDisposisiIcon.contentMode = .scaleAspectFit modernDisposisiIcon.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ modernDisposisiIcon.widthAnchor.constraint(equalToConstant: 13), modernDisposisiIcon.heightAnchor.constraint(equalToConstant: 13) ]) disposisiStack.addArrangedSubview(modernDisposisiIcon) modernDisposisiLabel.font = UIFont.systemFont(ofSize: 11, weight: .medium) modernDisposisiLabel.textColor = UIColor(red: 50/255, green: 70/255, blue: 95/255, alpha: 1.0) modernDisposisiLabel.numberOfLines = 1 disposisiStack.addArrangedSubview(modernDisposisiLabel) let spacer = UIView() spacer.setContentHuggingPriority(.defaultLow, for: .horizontal) disposisiStack.addArrangedSubview(spacer) modernDetailLabel.font = UIFont.systemFont(ofSize: 11, weight: .bold) modernDetailLabel.textColor = UIColor.siraniPrimaryBlue modernDetailLabel.text = "Detail" disposisiStack.addArrangedSubview(modernDetailLabel) if #available(iOS 13.0, *) { modernChevronIcon.image = UIImage(systemName: "chevron.right") } modernChevronIcon.tintColor = UIColor.siraniPrimaryBlue modernChevronIcon.contentMode = .scaleAspectFit modernChevronIcon.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ modernChevronIcon.widthAnchor.constraint(equalToConstant: 10), modernChevronIcon.heightAnchor.constraint(equalToConstant: 10) ]) disposisiStack.addArrangedSubview(modernChevronIcon) NSLayoutConstraint.activate([ disposisiStack.topAnchor.constraint(equalTo: modernDisposisiCard.topAnchor, constant: 6), disposisiStack.bottomAnchor.constraint(equalTo: modernDisposisiCard.bottomAnchor, constant: -6), disposisiStack.leadingAnchor.constraint(equalTo: modernDisposisiCard.leadingAnchor, constant: 10), disposisiStack.trailingAnchor.constraint(equalTo: modernDisposisiCard.trailingAnchor, constant: -10) ]) NSLayoutConstraint.activate([ headerStack.topAnchor.constraint(equalTo: modernContainer.topAnchor), headerStack.leadingAnchor.constraint(equalTo: modernContainer.leadingAnchor), headerStack.trailingAnchor.constraint(equalTo: modernContainer.trailingAnchor), divider.topAnchor.constraint(equalTo: headerStack.bottomAnchor, constant: 10), divider.leadingAnchor.constraint(equalTo: modernContainer.leadingAnchor), divider.trailingAnchor.constraint(equalTo: modernContainer.trailingAnchor), divider.heightAnchor.constraint(equalToConstant: 1), modernEqpLabel.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 10), modernEqpLabel.leadingAnchor.constraint(equalTo: modernContainer.leadingAnchor), modernEqpLabel.trailingAnchor.constraint(equalTo: modernContainer.trailingAnchor), companyStack.topAnchor.constraint(equalTo: modernEqpLabel.bottomAnchor, constant: 6), companyStack.leadingAnchor.constraint(equalTo: modernContainer.leadingAnchor), companyStack.trailingAnchor.constraint(equalTo: modernContainer.trailingAnchor), modernSpecStack.topAnchor.constraint(equalTo: companyStack.bottomAnchor, constant: 10), modernSpecStack.leadingAnchor.constraint(equalTo: modernContainer.leadingAnchor), modernSpecStack.trailingAnchor.constraint(equalTo: modernContainer.trailingAnchor), modernDisposisiCard.topAnchor.constraint(equalTo: modernSpecStack.bottomAnchor, constant: 10), modernDisposisiCard.leadingAnchor.constraint(equalTo: modernContainer.leadingAnchor), modernDisposisiCard.trailingAnchor.constraint(equalTo: modernContainer.trailingAnchor), modernDisposisiCard.bottomAnchor.constraint(equalTo: modernContainer.bottomAnchor) ]) } func updateViews(aplData: Aplikasi) { setupCardVisuals() setupModernLayout() card.isInteractable = true card.isUserInteractionEnabled = true modernContainer.isUserInteractionEnabled = true modernDisposisiCard.isUserInteractionEnabled = true if let gestures = card.gestureRecognizers { for g in gestures { card.removeGestureRecognizer(g) } } let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onItemTap)) card.addGestureRecognizer(gesture) if let disposisiGestures = modernDisposisiCard.gestureRecognizers { for g in disposisiGestures { modernDisposisiCard.removeGestureRecognizer(g) } } let disposisiGesture = UITapGestureRecognizer(target: self, action: #selector(self.onItemTap)) modernDisposisiCard.addGestureRecognizer(disposisiGesture) modernAplIdLabel.text = "APL ID: \(aplData.apl_id)" lblAplId?.text = "\(aplData.apl_id)" let idIzinStr = aplData.id_izin.isEmpty ? "-" : aplData.id_izin modernIdIzinLabel.text = "ID Izin: \(idIzinStr)" lblIdIzin?.text = idIzinStr let statusStr = aplData.StatusStr.trimmingCharacters(in: .whitespacesAndNewlines) modernStatusLabel.text = statusStr.isEmpty ? "PROSES" : statusStr.uppercased() if let statusColor = UIColor(hexString: aplData.StatusBgColor) { modernStatusPill.backgroundColor = statusColor.withAlphaComponent(0.15) modernStatusLabel.textColor = statusColor } else { modernStatusPill.backgroundColor = UIColor(red: 235/255, green: 242/255, blue: 252/255, alpha: 1.0) modernStatusLabel.textColor = UIColor.siraniPrimaryBlue } modernEqpLabel.text = aplData.eqp_name.isEmpty ? "-" : aplData.eqp_name modernCustLabel.text = aplData.cust_name.isEmpty ? "-" : aplData.cust_name var mm = aplData.merk if !aplData.model.isEmpty && aplData.model != "null" { mm = mm.isEmpty ? aplData.model : "\(mm) • \(aplData.model)" } modernMerkVal.text = mm.isEmpty ? "-" : mm modernResiVal.text = aplData.resi_no.isEmpty ? "-" : aplData.resi_no let roleId = UserState.instance.getRoleId() if roleId == "17" || aplData.disposisi.isEmpty { let resiDate = CommonHelper.formatDate(from: aplData.resi_date) modernDisposisiLabel.text = resiDate == "-" ? "Permohonan Layanan DJID" : "Tanggal Resi: \(resiDate)" } else { modernDisposisiLabel.text = "Disposisi: \(aplData.disposisi)" } self.dataAplikasi = aplData } @objc func onItemTap(sender: UITapGestureRecognizer) { cellDelegate?.openAplikasiDetail(data: dataAplikasi) } }