// // DashboardView.swift // App for Indeed // import Cocoa final class DashboardView: NSView { private let contentStack = NSStackView() private let documentContainer = NSView() private let chromeContainer = NSView() private let sidebar = NSStackView() private let mainColumn = NSStackView() private let greetingLabel = NSTextField(labelWithString: "") private let subtitleLabel = NSTextField(labelWithString: "Find your perfect job with the power of AI.") private let heroCard = NSView() private let statGrid = NSGridView(views: [[]]) private let recommendationsStack = NSStackView() private let insightsStack = NSStackView() private let scrollView = NSScrollView() private var recommendationsWidthConstraint: NSLayoutConstraint? private var insightsWidthConstraint: NSLayoutConstraint? override init(frame frameRect: NSRect) { super.init(frame: frameRect) setupLayout() } required init?(coder: NSCoder) { super.init(coder: coder) setupLayout() } override func layout() { super.layout() updateDocumentLayout() } func render(_ data: DashboardData) { greetingLabel.stringValue = "Welcome back, \(data.greetingName)! 👋" configureSidebar(data.sidebarItems) configureStats(data.stats) configureRecommendations(data.recommendations) configureInsights(data.insights) updateDocumentLayout() } private func setupLayout() { wantsLayer = true layer?.backgroundColor = NSColor(calibratedRed: 0.03, green: 0.06, blue: 0.14, alpha: 1).cgColor scrollView.translatesAutoresizingMaskIntoConstraints = false scrollView.hasVerticalScroller = true scrollView.drawsBackground = false addSubview(scrollView) contentStack.orientation = .horizontal contentStack.spacing = 20 contentStack.translatesAutoresizingMaskIntoConstraints = false contentStack.alignment = .top contentStack.edgeInsets = NSEdgeInsets(top: 24, left: 24, bottom: 24, right: 24) documentContainer.translatesAutoresizingMaskIntoConstraints = true documentContainer.autoresizingMask = [.width] documentContainer.frame = NSRect(x: 0, y: 0, width: 1040, height: 900) chromeContainer.translatesAutoresizingMaskIntoConstraints = false chromeContainer.wantsLayer = true chromeContainer.layer?.backgroundColor = NSColor(calibratedRed: 0.03, green: 0.08, blue: 0.2, alpha: 1).cgColor chromeContainer.layer?.cornerRadius = 18 documentContainer.addSubview(chromeContainer) chromeContainer.addSubview(contentStack) scrollView.documentView = documentContainer sidebar.orientation = .vertical sidebar.spacing = 10 sidebar.alignment = .leading sidebar.wantsLayer = true sidebar.layer?.backgroundColor = NSColor(calibratedRed: 0.06, green: 0.09, blue: 0.2, alpha: 1).cgColor sidebar.layer?.cornerRadius = 16 sidebar.edgeInsets = NSEdgeInsets(top: 18, left: 14, bottom: 18, right: 14) sidebar.translatesAutoresizingMaskIntoConstraints = false mainColumn.orientation = .vertical mainColumn.spacing = 14 mainColumn.alignment = .leading mainColumn.translatesAutoresizingMaskIntoConstraints = false greetingLabel.font = .systemFont(ofSize: 30, weight: .bold) greetingLabel.textColor = .white subtitleLabel.font = .systemFont(ofSize: 14, weight: .regular) subtitleLabel.textColor = NSColor.white.withAlphaComponent(0.75) heroCard.wantsLayer = true heroCard.layer?.backgroundColor = NSColor(calibratedRed: 0.08, green: 0.11, blue: 0.28, alpha: 1).cgColor heroCard.layer?.cornerRadius = 18 heroCard.translatesAutoresizingMaskIntoConstraints = false let hero = buildHeroContent() heroCard.addSubview(hero) statGrid.translatesAutoresizingMaskIntoConstraints = false statGrid.rowSpacing = 10 statGrid.columnSpacing = 10 let lowerSection = NSStackView() lowerSection.orientation = .horizontal lowerSection.spacing = 12 lowerSection.alignment = .top lowerSection.distribution = .fill lowerSection.translatesAutoresizingMaskIntoConstraints = false let recommendationsBox = sectionBox(title: "Recommended for You", content: recommendationsStack) let insightsBox = sectionBox(title: "AI Insights", content: insightsStack) recommendationsBox.translatesAutoresizingMaskIntoConstraints = false insightsBox.translatesAutoresizingMaskIntoConstraints = false lowerSection.addArrangedSubview(recommendationsBox) lowerSection.addArrangedSubview(insightsBox) mainColumn.addArrangedSubview(greetingLabel) mainColumn.addArrangedSubview(subtitleLabel) mainColumn.addArrangedSubview(heroCard) mainColumn.addArrangedSubview(statGrid) mainColumn.addArrangedSubview(lowerSection) contentStack.addArrangedSubview(sidebar) contentStack.addArrangedSubview(mainColumn) NSLayoutConstraint.activate([ scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), scrollView.topAnchor.constraint(equalTo: topAnchor), scrollView.bottomAnchor.constraint(equalTo: bottomAnchor), chromeContainer.topAnchor.constraint(equalTo: documentContainer.topAnchor, constant: 18), chromeContainer.bottomAnchor.constraint(equalTo: documentContainer.bottomAnchor, constant: -18), chromeContainer.centerXAnchor.constraint(equalTo: documentContainer.centerXAnchor), chromeContainer.widthAnchor.constraint(lessThanOrEqualToConstant: 1240), chromeContainer.widthAnchor.constraint(equalTo: documentContainer.widthAnchor, constant: -36), contentStack.leadingAnchor.constraint(equalTo: chromeContainer.leadingAnchor), contentStack.trailingAnchor.constraint(equalTo: chromeContainer.trailingAnchor), contentStack.topAnchor.constraint(equalTo: chromeContainer.topAnchor), contentStack.bottomAnchor.constraint(equalTo: chromeContainer.bottomAnchor), sidebar.widthAnchor.constraint(equalToConstant: 225), mainColumn.widthAnchor.constraint(greaterThanOrEqualToConstant: 720), heroCard.widthAnchor.constraint(equalTo: mainColumn.widthAnchor), heroCard.heightAnchor.constraint(equalToConstant: 140), hero.leadingAnchor.constraint(equalTo: heroCard.leadingAnchor, constant: 22), hero.trailingAnchor.constraint(equalTo: heroCard.trailingAnchor, constant: -22), hero.topAnchor.constraint(equalTo: heroCard.topAnchor, constant: 18), hero.bottomAnchor.constraint(equalTo: heroCard.bottomAnchor, constant: -18), lowerSection.widthAnchor.constraint(equalTo: mainColumn.widthAnchor) ]) recommendationsWidthConstraint = recommendationsBox.widthAnchor.constraint(equalTo: mainColumn.widthAnchor, multiplier: 0.68) insightsWidthConstraint = insightsBox.widthAnchor.constraint(equalTo: mainColumn.widthAnchor, multiplier: 0.32, constant: -8) recommendationsWidthConstraint?.isActive = true insightsWidthConstraint?.isActive = true } private func buildHeroContent() -> NSView { let container = NSStackView() container.orientation = .horizontal container.translatesAutoresizingMaskIntoConstraints = false container.distribution = .fillEqually let left = NSStackView() left.orientation = .vertical left.spacing = 8 left.alignment = .leading let title = NSTextField(labelWithString: "AI Job Search Assistant") title.font = .systemFont(ofSize: 24, weight: .semibold) title.textColor = .white let body = NSTextField(labelWithString: "Let AI find the best jobs for you on Indeed based on your preferences.") body.font = .systemFont(ofSize: 12, weight: .regular) body.textColor = NSColor.white.withAlphaComponent(0.72) let action = NSButton(title: "Find Jobs with AI", target: nil, action: nil) action.bezelStyle = .rounded action.wantsLayer = true action.layer?.backgroundColor = NSColor(calibratedRed: 0.27, green: 0.42, blue: 1.0, alpha: 1).cgColor action.layer?.cornerRadius = 8 action.contentTintColor = .white action.font = .systemFont(ofSize: 13, weight: .semibold) left.addArrangedSubview(title) left.addArrangedSubview(body) left.addArrangedSubview(action) let right = NSStackView() right.orientation = .vertical right.alignment = .trailing right.addArrangedSubview(tagBubble("Quick")) right.addArrangedSubview(tagBubble("Smart")) right.addArrangedSubview(tagBubble("Personalized")) container.addArrangedSubview(left) container.addArrangedSubview(right) return container } private func configureSidebar(_ items: [SidebarItem]) { sidebar.arrangedSubviews.forEach { sidebar.removeArrangedSubview($0) $0.removeFromSuperview() } let brand = NSTextField(labelWithString: "Indeed AI\nJob Finder") brand.font = .systemFont(ofSize: 18, weight: .bold) brand.textColor = .white sidebar.addArrangedSubview(brand) let spacer = NSView() spacer.translatesAutoresizingMaskIntoConstraints = false spacer.heightAnchor.constraint(equalToConstant: 8).isActive = true sidebar.addArrangedSubview(spacer) items.enumerated().forEach { index, item in let row = NSStackView() row.orientation = .horizontal row.spacing = 8 row.alignment = .centerY row.wantsLayer = true row.layer?.cornerRadius = 8 row.edgeInsets = NSEdgeInsets(top: 8, left: 10, bottom: 8, right: 10) if index == 0 { row.layer?.backgroundColor = NSColor(calibratedRed: 0.22, green: 0.31, blue: 0.85, alpha: 0.4).cgColor } let icon = NSImageView() icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 13, weight: .medium) icon.image = NSImage(systemSymbolName: item.systemImage, accessibilityDescription: item.title) icon.contentTintColor = .white let text = NSTextField(labelWithString: item.title) text.font = .systemFont(ofSize: 14, weight: .medium) text.textColor = .white row.addArrangedSubview(icon) row.addArrangedSubview(text) if let badge = item.badge { let badgeField = NSTextField(labelWithString: badge) badgeField.font = .systemFont(ofSize: 11, weight: .semibold) badgeField.textColor = .white badgeField.wantsLayer = true badgeField.layer?.backgroundColor = NSColor.systemPurple.cgColor badgeField.layer?.cornerRadius = 8 badgeField.alignment = .center badgeField.maximumNumberOfLines = 1 badgeField.lineBreakMode = .byClipping badgeField.translatesAutoresizingMaskIntoConstraints = false badgeField.widthAnchor.constraint(equalToConstant: 42).isActive = true row.addArrangedSubview(NSView()) row.addArrangedSubview(badgeField) } sidebar.addArrangedSubview(row) } } private func configureStats(_ stats: [StatCard]) { statGrid.removeRow(at: 0) let cards = stats.map { stat -> NSView in let card = NSStackView() card.orientation = .vertical card.spacing = 6 card.edgeInsets = NSEdgeInsets(top: 14, left: 14, bottom: 14, right: 14) card.wantsLayer = true card.layer?.backgroundColor = NSColor(calibratedRed: 0.06, green: 0.11, blue: 0.24, alpha: 1).cgColor card.layer?.cornerRadius = 14 let value = NSTextField(labelWithString: stat.value) value.font = .systemFont(ofSize: 30, weight: .bold) value.textColor = .white let title = NSTextField(labelWithString: stat.title) title.font = .systemFont(ofSize: 13, weight: .medium) title.textColor = NSColor.white.withAlphaComponent(0.74) let trend = NSTextField(labelWithString: stat.trend) trend.font = .systemFont(ofSize: 12, weight: .semibold) trend.textColor = NSColor.systemGreen card.addArrangedSubview(value) card.addArrangedSubview(title) card.addArrangedSubview(trend) card.translatesAutoresizingMaskIntoConstraints = false card.widthAnchor.constraint(equalToConstant: 185).isActive = true card.heightAnchor.constraint(equalToConstant: 120).isActive = true return card } statGrid.addRow(with: cards) } private func configureRecommendations(_ recommendations: [JobRecommendation]) { recommendationsStack.orientation = .vertical recommendationsStack.spacing = 10 recommendationsStack.alignment = .leading recommendationsStack.arrangedSubviews.forEach { recommendationsStack.removeArrangedSubview($0) $0.removeFromSuperview() } recommendations.forEach { recommendation in let row = NSStackView() row.orientation = .horizontal row.spacing = 12 row.alignment = .centerY let icon = NSView() icon.wantsLayer = true icon.layer?.cornerRadius = 10 icon.layer?.backgroundColor = NSColor(calibratedRed: 0.19, green: 0.34, blue: 0.9, alpha: 0.9).cgColor icon.translatesAutoresizingMaskIntoConstraints = false icon.widthAnchor.constraint(equalToConstant: 38).isActive = true icon.heightAnchor.constraint(equalToConstant: 38).isActive = true let textColumn = NSStackView() textColumn.orientation = .vertical textColumn.spacing = 2 textColumn.alignment = .leading let title = NSTextField(labelWithString: recommendation.title) title.font = .systemFont(ofSize: 15, weight: .semibold) title.textColor = .white let subtitle = NSTextField(labelWithString: "\(recommendation.company) • \(recommendation.location)") subtitle.font = .systemFont(ofSize: 12, weight: .regular) subtitle.textColor = NSColor.white.withAlphaComponent(0.7) textColumn.addArrangedSubview(title) textColumn.addArrangedSubview(subtitle) let meta = NSStackView() meta.orientation = .vertical meta.alignment = .trailing let match = NSTextField(labelWithString: recommendation.matchRate) match.font = .systemFont(ofSize: 12, weight: .semibold) match.textColor = NSColor.systemGreen let posted = NSTextField(labelWithString: recommendation.postedAgo) posted.font = .systemFont(ofSize: 11, weight: .regular) posted.textColor = NSColor.white.withAlphaComponent(0.6) meta.addArrangedSubview(match) meta.addArrangedSubview(posted) row.addArrangedSubview(icon) row.addArrangedSubview(textColumn) row.addArrangedSubview(NSView()) row.addArrangedSubview(meta) recommendationsStack.addArrangedSubview(row) } } private func configureInsights(_ insights: [InsightItem]) { insightsStack.orientation = .vertical insightsStack.spacing = 12 insightsStack.alignment = .leading insightsStack.arrangedSubviews.forEach { insightsStack.removeArrangedSubview($0) $0.removeFromSuperview() } insights.forEach { insight in let card = NSStackView() card.orientation = .vertical card.spacing = 4 card.edgeInsets = NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) card.wantsLayer = true card.layer?.backgroundColor = NSColor(calibratedRed: 0.07, green: 0.12, blue: 0.22, alpha: 1).cgColor card.layer?.cornerRadius = 10 let title = NSTextField(labelWithString: insight.title) title.font = .systemFont(ofSize: 14, weight: .semibold) title.textColor = .white let body = NSTextField(labelWithString: insight.description) body.font = .systemFont(ofSize: 12, weight: .regular) body.textColor = NSColor.white.withAlphaComponent(0.7) body.maximumNumberOfLines = 2 body.lineBreakMode = .byWordWrapping card.addArrangedSubview(title) card.addArrangedSubview(body) insightsStack.addArrangedSubview(card) } } private func sectionBox(title: String, content: NSStackView) -> NSView { let box = NSStackView() box.orientation = .vertical box.spacing = 12 box.alignment = .leading box.edgeInsets = NSEdgeInsets(top: 14, left: 14, bottom: 14, right: 14) box.wantsLayer = true box.layer?.backgroundColor = NSColor(calibratedRed: 0.05, green: 0.09, blue: 0.19, alpha: 1).cgColor box.layer?.cornerRadius = 16 let titleLabel = NSTextField(labelWithString: title) titleLabel.font = .systemFont(ofSize: 18, weight: .semibold) titleLabel.textColor = .white box.addArrangedSubview(titleLabel) box.addArrangedSubview(content) return box } private func tagBubble(_ text: String) -> NSView { let label = NSTextField(labelWithString: text) label.font = .systemFont(ofSize: 11, weight: .medium) label.textColor = NSColor(calibratedRed: 0.75, green: 0.79, blue: 1, alpha: 1) label.wantsLayer = true label.layer?.backgroundColor = NSColor(calibratedRed: 0.2, green: 0.24, blue: 0.45, alpha: 0.5).cgColor label.layer?.cornerRadius = 7 label.alignment = .center label.translatesAutoresizingMaskIntoConstraints = false label.widthAnchor.constraint(equalToConstant: 90).isActive = true return label } private func updateDocumentLayout() { documentContainer.layoutSubtreeIfNeeded() let fittingHeight = max(chromeContainer.fittingSize.height + 36, bounds.height) documentContainer.frame = NSRect(x: 0, y: 0, width: bounds.width, height: fittingHeight) } }