Skip to content

Commit 24cc36e

Browse files
committed
Fetch & populate data for the ProductCard Top Performers Section
1 parent 93d60a9 commit 24cc36e

File tree

1 file changed

+67
-13
lines changed

1 file changed

+67
-13
lines changed

WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class AnalyticsHubViewModel: ObservableObject {
3737

3838
/// Products Card ViewModel
3939
///
40-
@Published var productCard = AnalyticsHubViewModel.productCard(currentPeriodStats: nil, previousPeriodStats: nil)
40+
@Published var productCard = AnalyticsHubViewModel.productCard(currentPeriodStats: nil, previousPeriodStats: nil, itemsSoldStats: nil)
4141

4242
/// Time Range Selection Type
4343
///
@@ -57,6 +57,10 @@ final class AnalyticsHubViewModel: ObservableObject {
5757
///
5858
@Published private var previousOrderStats: OrderStatsV4? = nil
5959

60+
/// Stats for the current top items sold. Used in the products card.
61+
///
62+
@Published private var itemsSoldStats: TopEarnerStats? = nil
63+
6064
/// Time Range selection data defining the current and previous time period
6165
///
6266
private var timeRangeSelection: AnalyticsHubTimeRangeSelection
@@ -89,9 +93,15 @@ private extension AnalyticsHubViewModel {
8993
async let previousPeriodRequest = retrieveStats(earliestDateToInclude: previousTimeRange.start,
9094
latestDateToInclude: previousTimeRange.end,
9195
forceRefresh: true)
92-
let (currentPeriodStats, previousPeriodStats) = try await (currentPeriodRequest, previousPeriodRequest)
96+
97+
async let itemsSoldRequest = retrieveTopItemsSoldStats(earliestDateToInclude: currentTimeRange.start,
98+
latestDateToInclude: currentTimeRange.end,
99+
forceRefresh: true)
100+
101+
let (currentPeriodStats, previousPeriodStats, itemsSoldStats) = try await (currentPeriodRequest, previousPeriodRequest, itemsSoldRequest)
93102
self.currentOrderStats = currentPeriodStats
94103
self.previousOrderStats = previousPeriodStats
104+
self.itemsSoldStats = itemsSoldStats
95105
}
96106

97107
@MainActor
@@ -114,6 +124,25 @@ private extension AnalyticsHubViewModel {
114124
stores.dispatch(action)
115125
}
116126
}
127+
128+
@MainActor
129+
/// Retrieves top ItemsSold stats using the `retrieveTopEarnerStats` action but without saving results into storage.
130+
///
131+
func retrieveTopItemsSoldStats(earliestDateToInclude: Date, latestDateToInclude: Date, forceRefresh: Bool) async throws -> TopEarnerStats {
132+
try await withCheckedThrowingContinuation { continuation in
133+
let action = StatsActionV4.retrieveTopEarnerStats(siteID: siteID,
134+
timeRange: .thisYear, // Only needed for storing purposes, we can ignore it.
135+
earliestDateToInclude: earliestDateToInclude,
136+
latestDateToInclude: latestDateToInclude,
137+
quantity: Constants.maxNumberOfTopItemsSold,
138+
forceRefresh: forceRefresh,
139+
saveInStorage: false,
140+
onCompletion: { result in
141+
continuation.resume(with: result)
142+
})
143+
stores.dispatch(action)
144+
}
145+
}
117146
}
118147

119148
// MARK: Data - UI mapping
@@ -128,16 +157,19 @@ private extension AnalyticsHubViewModel {
128157
func switchToErrorState() {
129158
self.currentOrderStats = nil
130159
self.previousOrderStats = nil
160+
self.itemsSoldStats = nil
131161
}
132162

133163
func bindViewModelsWithData() {
134-
Publishers.CombineLatest($currentOrderStats, $previousOrderStats)
135-
.sink { [weak self] currentOrderStats, previousOrderStats in
164+
Publishers.CombineLatest3($currentOrderStats, $previousOrderStats, $itemsSoldStats)
165+
.sink { [weak self] currentOrderStats, previousOrderStats, itemsSoldStats in
136166
guard let self else { return }
137167

138168
self.revenueCard = AnalyticsHubViewModel.revenueCard(currentPeriodStats: currentOrderStats, previousPeriodStats: previousOrderStats)
139169
self.ordersCard = AnalyticsHubViewModel.ordersCard(currentPeriodStats: currentOrderStats, previousPeriodStats: previousOrderStats)
140-
self.productCard = AnalyticsHubViewModel.productCard(currentPeriodStats: currentOrderStats, previousPeriodStats: previousOrderStats)
170+
self.productCard = AnalyticsHubViewModel.productCard(currentPeriodStats: currentOrderStats,
171+
previousPeriodStats: previousOrderStats,
172+
itemsSoldStats: itemsSoldStats)
141173

142174
}.store(in: &subscriptions)
143175

@@ -197,25 +229,38 @@ private extension AnalyticsHubViewModel {
197229
syncErrorMessage: Localization.OrderCard.noOrders)
198230
}
199231

200-
static func productCard(currentPeriodStats: OrderStatsV4?, previousPeriodStats: OrderStatsV4?) -> AnalyticsProductCardViewModel {
232+
/// Helper function to create a `AnalyticsReportCardViewModel` from the fetched stats.
233+
///
234+
static func productCard(currentPeriodStats: OrderStatsV4?,
235+
previousPeriodStats: OrderStatsV4?,
236+
itemsSoldStats: TopEarnerStats?) -> AnalyticsProductCardViewModel {
201237
let showSyncError = currentPeriodStats == nil || previousPeriodStats == nil
202238
let itemsSold = StatsDataTextFormatter.createItemsSoldText(orderStats: currentPeriodStats)
203239
let itemsSoldDelta = StatsDataTextFormatter.createOrderItemsSoldDelta(from: previousPeriodStats, to: currentPeriodStats)
204240

205-
let imageURL = URL(string: "https://s0.wordpress.com/i/store/mobile/plans-premium.png")
206241
return AnalyticsProductCardViewModel(itemsSold: itemsSold,
207242
delta: itemsSoldDelta.string,
208243
deltaBackgroundColor: Constants.deltaColor(for: itemsSoldDelta.direction),
209-
itemsSoldData: [ // Temporary data
210-
.init(imageURL: imageURL, name: "Tabletop Photos", details: "Net Sales: $1,232", value: "32"),
211-
.init(imageURL: imageURL, name: "Kentya Palm", details: "Net Sales: $800", value: "10"),
212-
.init(imageURL: imageURL, name: "Love Ficus", details: "Net Sales: $599", value: "5"),
213-
.init(imageURL: imageURL, name: "Bird Of Paradise", details: "Net Sales: $23.50", value: "2")
214-
],
244+
itemsSoldData: itemSoldRows(from: itemsSoldStats),
215245
isRedacted: false,
216246
showSyncError: showSyncError)
217247
}
218248

249+
/// Helper functions to create `TopPerformersRow.Data` items rom the provided `TopEarnerStats`.
250+
///
251+
static func itemSoldRows(from itemSoldStats: TopEarnerStats?) -> [TopPerformersRow.Data] {
252+
guard let items = itemSoldStats?.items else {
253+
return []
254+
}
255+
256+
return items.map { item in
257+
TopPerformersRow.Data(imageURL: URL(string: item.imageUrl ?? ""),
258+
name: item.productName ?? "",
259+
details: Localization.ProductCard.netSales(value: item.totalString),
260+
value: "\(item.quantity)")
261+
}
262+
}
263+
219264
static func timeRangeCard(timeRangeSelection: AnalyticsHubTimeRangeSelection) -> AnalyticsTimeRangeCardViewModel {
220265
return AnalyticsTimeRangeCardViewModel(selectedRangeTitle: timeRangeSelection.rangeSelectionDescription,
221266
currentRangeSubtitle: timeRangeSelection.currentRangeDescription,
@@ -226,6 +271,8 @@ private extension AnalyticsHubViewModel {
226271
// MARK: - Constants
227272
private extension AnalyticsHubViewModel {
228273
enum Constants {
274+
static let maxNumberOfTopItemsSold = 5
275+
229276
static func deltaColor(for direction: StatsDataTextFormatter.DeltaPercentage.Direction) -> UIColor {
230277
switch direction {
231278
case .positive:
@@ -252,5 +299,12 @@ private extension AnalyticsHubViewModel {
252299
static let noOrders = NSLocalizedString("Unable to load order analytics",
253300
comment: "Text displayed when there is an error loading order stats data.")
254301
}
302+
303+
enum ProductCard {
304+
static func netSales(value: String) -> String {
305+
String.localizedStringWithFormat(NSLocalizedString("Net sales: %@", comment: "Label for the total sales of a product in the Analytics Hub"),
306+
value)
307+
}
308+
}
255309
}
256310
}

0 commit comments

Comments
 (0)