Просмотр исходного кода

Add dashboard UI architecture and connect main window flow.

Introduce dashboard models, views, and controller wiring, update the storyboard launch behavior, and switch the main view controller to the new dashboard implementation.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 2 месяцев назад
Родитель
Сommit
7d11c65379

+ 1 - 1
App for Indeed/Base.lproj/Main.storyboard

@@ -683,7 +683,7 @@
         <scene sceneID="R2V-B0-nI4">
             <objects>
                 <windowController id="B8D-0N-5wS" sceneMemberID="viewController">
-                    <window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
+                    <window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="YES" animationBehavior="default" id="IQv-IB-iLA">
                         <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
                         <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
                         <rect key="contentRect" x="196" y="240" width="480" height="270"/>

+ 32 - 0
App for Indeed/Controllers/DashboardViewController.swift

@@ -0,0 +1,32 @@
+//
+//  DashboardViewController.swift
+//  App for Indeed
+//
+
+import Cocoa
+
+class DashboardViewController: NSViewController {
+    private let dataProvider: DashboardDataProviding
+    private let dashboardView = DashboardView(frame: .zero)
+
+    init(dataProvider: DashboardDataProviding = MockDashboardDataProvider()) {
+        self.dataProvider = dataProvider
+        super.init(nibName: nil, bundle: nil)
+    }
+
+    @available(*, unavailable)
+    required init?(coder: NSCoder) {
+        self.dataProvider = MockDashboardDataProvider()
+        super.init(coder: coder)
+    }
+
+    override func loadView() {
+        view = dashboardView
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        let data = dataProvider.loadDashboardData()
+        dashboardView.render(data)
+    }
+}

+ 77 - 0
App for Indeed/Models/DashboardModels.swift

@@ -0,0 +1,77 @@
+//
+//  DashboardModels.swift
+//  App for Indeed
+//
+
+import Foundation
+
+struct SidebarItem {
+    let title: String
+    let systemImage: String
+    let badge: String?
+}
+
+struct StatCard {
+    let title: String
+    let value: String
+    let trend: String
+}
+
+struct JobRecommendation {
+    let title: String
+    let company: String
+    let location: String
+    let matchRate: String
+    let postedAgo: String
+}
+
+struct InsightItem {
+    let title: String
+    let description: String
+    let systemImage: String
+}
+
+struct DashboardData {
+    let greetingName: String
+    let sidebarItems: [SidebarItem]
+    let stats: [StatCard]
+    let recommendations: [JobRecommendation]
+    let insights: [InsightItem]
+}
+
+protocol DashboardDataProviding {
+    func loadDashboardData() -> DashboardData
+}
+
+final class MockDashboardDataProvider: DashboardDataProviding {
+    func loadDashboardData() -> DashboardData {
+        DashboardData(
+            greetingName: "Olivia",
+            sidebarItems: [
+                SidebarItem(title: "Overview", systemImage: "house.fill", badge: nil),
+                SidebarItem(title: "AI Job Search", systemImage: "wand.and.stars", badge: "New"),
+                SidebarItem(title: "My Jobs", systemImage: "bookmark", badge: nil),
+                SidebarItem(title: "Saved Jobs", systemImage: "heart", badge: nil),
+                SidebarItem(title: "Applications", systemImage: "doc.text", badge: nil),
+                SidebarItem(title: "Profile", systemImage: "person", badge: nil),
+                SidebarItem(title: "Settings", systemImage: "gearshape", badge: nil)
+            ],
+            stats: [
+                StatCard(title: "Jobs Found", value: "128", trend: "+24 this week"),
+                StatCard(title: "Saved Jobs", value: "32", trend: "+6 this week"),
+                StatCard(title: "Applications", value: "14", trend: "+3 this week"),
+                StatCard(title: "Interviews", value: "5", trend: "Upcoming")
+            ],
+            recommendations: [
+                JobRecommendation(title: "Senior Product Designer", company: "Dropbox", location: "San Francisco, CA", matchRate: "95% Match", postedAgo: "2h ago"),
+                JobRecommendation(title: "UX Researcher", company: "Airbnb", location: "Remote", matchRate: "90% Match", postedAgo: "4h ago"),
+                JobRecommendation(title: "Product Manager", company: "Stripe", location: "New York, NY", matchRate: "88% Match", postedAgo: "6h ago")
+            ],
+            insights: [
+                InsightItem(title: "High Demand", description: "Product designer roles are in high demand right now.", systemImage: "chart.line.uptrend.xyaxis"),
+                InsightItem(title: "Improve Your Profile", description: "Add more skills to increase your match rate.", systemImage: "person.crop.circle.badge.checkmark"),
+                InsightItem(title: "New Opportunities", description: "24 new jobs match your profile this week.", systemImage: "briefcase.fill")
+            ]
+        )
+    }
+}

+ 1 - 16
App for Indeed/ViewController.swift

@@ -7,20 +7,5 @@
 
 import Cocoa
 
-class ViewController: NSViewController {
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        // Do any additional setup after loading the view.
-    }
-
-    override var representedObject: Any? {
-        didSet {
-        // Update the view, if already loaded.
-        }
-    }
-
-
-}
+final class ViewController: DashboardViewController {}
 

+ 414 - 0
App for Indeed/Views/DashboardView.swift

@@ -0,0 +1,414 @@
+//
+//  DashboardView.swift
+//  App for Indeed
+//
+
+import Cocoa
+
+final class DashboardView: NSView {
+    private let contentStack = NSStackView()
+    private let documentContainer = 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()
+
+    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.edgeInsets = NSEdgeInsets(top: 20, left: 20, bottom: 24, right: 20)
+
+        documentContainer.translatesAutoresizingMaskIntoConstraints = true
+        documentContainer.autoresizingMask = [.width]
+        documentContainer.frame = NSRect(x: 0, y: 0, width: 1040, height: 900)
+        documentContainer.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.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),
+
+            contentStack.leadingAnchor.constraint(equalTo: documentContainer.leadingAnchor),
+            contentStack.trailingAnchor.constraint(equalTo: documentContainer.trailingAnchor),
+            contentStack.topAnchor.constraint(equalTo: documentContainer.topAnchor),
+            contentStack.bottomAnchor.constraint(equalTo: documentContainer.bottomAnchor),
+            contentStack.widthAnchor.constraint(equalTo: documentContainer.widthAnchor),
+
+            sidebar.widthAnchor.constraint(equalToConstant: 225),
+            mainColumn.widthAnchor.constraint(greaterThanOrEqualToConstant: 760),
+            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),
+
+            recommendationsBox.widthAnchor.constraint(equalToConstant: 510),
+            insightsBox.widthAnchor.constraint(equalToConstant: 238)
+        ])
+    }
+
+    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(contentStack.fittingSize.height, bounds.height)
+        documentContainer.frame = NSRect(x: 0, y: 0, width: bounds.width, height: fittingHeight)
+    }
+}