Skip to content

Commit 2ed4650

Browse files
authored
[Woo POS][Historical Orders] Order List Row (Wireframe UI) (#16043)
2 parents 9d7f634 + 4f582e1 commit 2ed4650

File tree

6 files changed

+179
-20
lines changed

6 files changed

+179
-20
lines changed

Modules/Sources/Yosemite/Model/Payments/OrderPaymentMethod.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// Order Payment methods
2-
enum OrderPaymentMethod: RawRepresentable {
2+
public enum OrderPaymentMethod: RawRepresentable {
33
/// Booking (confirmed by Shop manager)
44
case booking
55

Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrder.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public struct POSOrder: Equatable, Hashable {
1313
public let status: OrderStatusEnum
1414
public let total: String
1515
public let customerEmail: String?
16+
public let paymentMethodID: String
1617
public let paymentMethodTitle: String
1718
public let lineItems: [POSOrderItem]
1819
public let refunds: [POSOrderRefund]
@@ -25,6 +26,7 @@ public struct POSOrder: Equatable, Hashable {
2526
status: OrderStatusEnum,
2627
total: String,
2728
customerEmail: String? = nil,
29+
paymentMethodID: String,
2830
paymentMethodTitle: String,
2931
lineItems: [POSOrderItem] = [],
3032
refunds: [POSOrderRefund] = [],
@@ -36,6 +38,7 @@ public struct POSOrder: Equatable, Hashable {
3638
self.status = status
3739
self.total = total
3840
self.customerEmail = customerEmail
41+
self.paymentMethodID = paymentMethodID
3942
self.paymentMethodTitle = paymentMethodTitle
4043
self.lineItems = lineItems
4144
self.refunds = refunds
@@ -63,6 +66,7 @@ public extension POSOrder {
6366
status: order.status,
6467
total: order.total,
6568
customerEmail: customerEmail,
69+
paymentMethodID: order.paymentMethodID,
6670
paymentMethodTitle: order.paymentMethodTitle,
6771
lineItems: posLineItems,
6872
refunds: posRefunds,

WooCommerce/Classes/POS/Models/OrdersViewState.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ enum OrderListState: Equatable {
2424
}
2525
}
2626

27+
var isEmpty: Bool {
28+
switch self {
29+
case .loaded:
30+
return false
31+
case .loading(let items):
32+
return items.isEmpty
33+
default:
34+
return true
35+
}
36+
}
2737

2838
var orders: [POSOrder] {
2939
switch self {

WooCommerce/Classes/POS/Presentation/Orders/PointOfSaleOrderListView.swift

Lines changed: 154 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import SwiftUI
22
import struct Yosemite.POSOrder
3+
import enum Yosemite.OrderPaymentMethod
4+
import WooFoundation
35

46
struct PointOfSaleOrderListView: View {
57
@Binding var selectedOrderID: String?
@@ -15,7 +17,7 @@ struct PointOfSaleOrderListView: View {
1517
var body: some View {
1618
VStack(spacing: 0) {
1719
POSPageHeaderView(
18-
title: "Orders",
20+
title: Localization.ordersTitle,
1921
isLoading: {
2022
if case .loading(let orders) = ordersViewState {
2123
return !orders.isEmpty
@@ -37,6 +39,7 @@ struct PointOfSaleOrderListView: View {
3739

3840
switch ordersViewState {
3941
case .empty:
42+
// TODO: WOOMOB-1139
4043
Text("No orders")
4144
case .error(let errorState):
4245
ItemListErrorCardView(errorState: errorState) {
@@ -54,6 +57,7 @@ struct PointOfSaleOrderListView: View {
5457
}
5558
.buttonStyle(PlainButtonStyle())
5659
}
60+
.animation(.default, value: orders.first?.id)
5761
}
5862

5963
footerRows
@@ -62,6 +66,7 @@ struct PointOfSaleOrderListView: View {
6266
}
6367
)
6468
}
69+
.animation(.default, value: orderListModel.ordersController.ordersViewState.isEmpty)
6570
.background(Color.posSurfaceBright)
6671
.navigationBarHidden(true)
6772
.refreshable {
@@ -92,10 +97,12 @@ struct PointOfSaleOrderListView: View {
9297
case .loading(let orders):
9398
if orders.isEmpty {
9499
ForEach(0..<8, id: \.self) { _ in
95-
GhostItemCardView()
100+
GhostOrderRowView()
96101
}
102+
.opacity(orders.isEmpty ? 1 : 0)
103+
.animation(.default, value: orders.isEmpty)
97104
} else {
98-
GhostItemCardView()
105+
GhostOrderRowView()
99106
}
100107
case .inlineError(_, let errorState, .pagination):
101108
ItemListErrorCardView(errorState: errorState) {
@@ -113,30 +120,161 @@ private struct OrderRowView: View {
113120
let order: POSOrder
114121
let isSelected: Bool
115122

123+
@ScaledMetric private var scale: CGFloat = 1.0
124+
@Environment(\.dynamicTypeSize) var dynamicTypeSize
125+
126+
private let currencyFormatter = CurrencyFormatter(currencySettings: ServiceLocator.currencySettings)
127+
128+
private var minHeight: CGFloat {
129+
min(Constants.orderCardMinHeight * scale, Constants.maximumOrderCardHeight)
130+
}
131+
132+
private var formattedTotal: String {
133+
currencyFormatter.formatAmount(order.total, with: order.currency) ?? ""
134+
}
135+
136+
var body: some View {
137+
HStack(alignment: .center, spacing: POSSpacing.medium) {
138+
VStack(alignment: .leading, spacing: POSSpacing.xSmall) {
139+
Text("#\(order.number)") // TODO: WOOMOB-1142
140+
.font(.posBodySmallBold)
141+
.foregroundStyle(Color.posOnSurface)
142+
.fixedSize(horizontal: false, vertical: true)
143+
144+
Text(DateFormatter.dateAndTimeFormatter.string(from: order.dateCreated))
145+
.font(.posBodySmallRegular())
146+
.foregroundStyle(Color.posOnSurfaceVariantHighest)
147+
.fixedSize(horizontal: false, vertical: true)
148+
149+
150+
if let customerEmail = order.customerEmail, customerEmail.isNotEmpty {
151+
Text(customerEmail)
152+
.font(.posBodySmallRegular())
153+
.foregroundStyle(Color.posOnSurfaceVariantHighest)
154+
.fixedSize(horizontal: false, vertical: true)
155+
}
156+
}
157+
.multilineTextAlignment(.leading)
158+
159+
Spacer()
160+
161+
VStack(alignment: .trailing, spacing: POSSpacing.xSmall) {
162+
Text(formattedTotal)
163+
.font(.posBodyLargeBold)
164+
.foregroundStyle(Color.posOnSurface)
165+
166+
HStack(spacing: POSSpacing.xSmall) {
167+
if let paymentMethodIcon = paymentMethodIcon {
168+
Image(systemName: paymentMethodIcon)
169+
.foregroundStyle(statusColor)
170+
.font(.caption)
171+
}
172+
Text(order.status.localizedName)
173+
.font(.posBodySmallRegular())
174+
.foregroundStyle(statusColor)
175+
}
176+
}
177+
.multilineTextAlignment(.trailing)
178+
}
179+
.padding(.horizontal, POSPadding.medium * (1 / scale))
180+
.padding(.vertical, POSPadding.medium * (1 / scale))
181+
.frame(maxWidth: .infinity, minHeight: dynamicTypeSize.isAccessibilitySize ? nil : minHeight, alignment: .leading)
182+
.background(isSelected ? Color.posSurfaceDim : Color.posSurfaceContainerLowest)
183+
.posItemCardBorderStyles()
184+
}
185+
}
186+
187+
private extension OrderRowView {
188+
var paymentMethodIcon: String? {
189+
let paymentMethod = OrderPaymentMethod(rawValue: order.paymentMethodID)
190+
switch paymentMethod {
191+
case .cod:
192+
return "banknote"
193+
case .stripe, .woocommercePayments:
194+
return "creditcard"
195+
default:
196+
return nil
197+
}
198+
}
199+
200+
var statusColor: Color {
201+
switch order.status {
202+
case .completed:
203+
return .posSuccess
204+
case .failed:
205+
return .posError
206+
default:
207+
return .posOnSurfaceVariantLowest
208+
}
209+
}
210+
}
211+
212+
private struct GhostOrderRowView: View {
213+
@ScaledMetric private var scale: CGFloat = 1.0
214+
@Environment(\.dynamicTypeSize) var dynamicTypeSize
215+
216+
private var minHeight: CGFloat {
217+
min(Constants.orderCardMinHeight * scale, Constants.maximumOrderCardHeight)
218+
}
219+
116220
var body: some View {
117-
VStack(alignment: .leading, spacing: 8) {
118-
HStack {
119-
Text("Order #\(order.number)")
120-
Spacer()
121-
Text("\(order.currencySymbol)\(order.total)")
221+
HStack(alignment: .center, spacing: POSSpacing.medium) {
222+
VStack(alignment: .leading, spacing: POSSpacing.xSmall) {
223+
Rectangle()
224+
.fill(Color.posOnSurfaceVariantLowest)
225+
.frame(width: 70, height: 16)
226+
.clipShape(RoundedRectangle(cornerRadius: 4))
227+
.shimmering()
228+
229+
Rectangle()
230+
.fill(Color.posOnSurfaceVariantLowest)
231+
.frame(width: 160, height: 14)
232+
.clipShape(RoundedRectangle(cornerRadius: 4))
233+
.shimmering()
122234
}
123235

124-
Text(order.dateCreated, style: .date)
236+
Spacer()
237+
238+
VStack(alignment: .trailing, spacing: POSSpacing.xSmall) {
239+
Rectangle()
240+
.fill(Color.posOnSurfaceVariantLowest)
241+
.frame(width: 80, height: 18)
242+
.clipShape(RoundedRectangle(cornerRadius: 4))
243+
.shimmering()
244+
245+
Rectangle()
246+
.fill(Color.posOnSurfaceVariantLowest)
247+
.frame(width: 90, height: 14)
248+
.clipShape(RoundedRectangle(cornerRadius: 4))
249+
.shimmering()
250+
}
125251
}
126-
.padding()
127-
.background(isSelected ? Color.accentColor.opacity(0.1) : Color.posSurface)
128-
.cornerRadius(8)
129-
.overlay(
130-
RoundedRectangle(cornerRadius: 8)
131-
.stroke(isSelected ? Color.accentColor : Color.clear, lineWidth: 2)
132-
)
252+
.padding(.horizontal, POSPadding.medium * (1 / scale))
253+
.padding(.vertical, POSPadding.medium * (1 / scale))
254+
.frame(maxWidth: .infinity, minHeight: dynamicTypeSize.isAccessibilitySize ? nil : minHeight, alignment: .leading)
255+
.background(Color.posSurfaceContainerLowest)
256+
.posItemCardBorderStyles()
257+
.geometryGroup()
133258
}
134259
}
135260

261+
private enum Constants {
262+
static let orderCardMinHeight: CGFloat = 90
263+
static let maximumOrderCardHeight: CGFloat = Constants.orderCardMinHeight * 2
264+
}
265+
266+
private enum Localization {
267+
static let ordersTitle = NSLocalizedString(
268+
"pos.orderListView.ordersTitle",
269+
value: "Orders",
270+
comment: "Title at the header for the Orders view.")
271+
}
272+
136273
#if DEBUG
137274
#Preview("List") {
138275
NavigationSplitView {
139276
PointOfSaleOrderListView(selectedOrderID: .constant("1"), onClose: {})
277+
.navigationSplitViewColumnWidth(450)
140278
.environment(POSPreviewHelpers.makePreviewOrdersModel())
141279
} detail: {
142280
Text("Detail View")

WooCommerce/Classes/POS/Utils/PreviewHelpers.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,8 @@ final class PointOfSalePreviewOrderListController: PointOfSaleOrderListControlle
251251
status: .completed,
252252
total: "25.00",
253253
customerEmail: "[email protected]",
254-
paymentMethodTitle: "Credit Card",
254+
paymentMethodID: "cod",
255+
paymentMethodTitle: "Cash",
255256
lineItems: [],
256257
refunds: [],
257258
currency: "USD",
@@ -263,8 +264,9 @@ final class PointOfSalePreviewOrderListController: PointOfSaleOrderListControlle
263264
dateCreated: Date().addingTimeInterval(-3600),
264265
status: .processing,
265266
total: "45.50",
266-
customerEmail: "[email protected]",
267-
paymentMethodTitle: "Cash",
267+
customerEmail: "[email protected]",
268+
paymentMethodID: "woocommerce_payments",
269+
paymentMethodTitle: "Credit Card",
268270
lineItems: [],
269271
refunds: [],
270272
currency: "USD",
@@ -277,6 +279,7 @@ final class PointOfSalePreviewOrderListController: PointOfSaleOrderListControlle
277279
status: .completed,
278280
total: "12.75",
279281
customerEmail: nil,
282+
paymentMethodID: "woocommerce_payments",
280283
paymentMethodTitle: "Credit Card",
281284
lineItems: [],
282285
refunds: [],

WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleOrderListService.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ extension MockPointOfSaleOrderListService {
5757
status: .completed,
5858
total: "25.99",
5959
customerEmail: "[email protected]",
60+
paymentMethodID: "cod",
6061
paymentMethodTitle: "Cash",
6162
lineItems: [
6263
POSOrderItem(itemID: 1, name: "Coffee", quantity: 2, total: "20.00"),
@@ -73,6 +74,7 @@ extension MockPointOfSaleOrderListService {
7374
status: .completed,
7475
total: "15.50",
7576
customerEmail: "[email protected]",
77+
paymentMethodID: "cod",
7678
paymentMethodTitle: "Card",
7779
lineItems: [
7880
POSOrderItem(itemID: 3, name: "Tea", quantity: 1, total: "15.50")
@@ -94,6 +96,7 @@ extension MockPointOfSaleOrderListService {
9496
status: .completed,
9597
total: "42.75",
9698
customerEmail: "[email protected]",
99+
paymentMethodID: "cod",
97100
paymentMethodTitle: "Cash",
98101
lineItems: [
99102
POSOrderItem(itemID: 4, name: "Sandwich", quantity: 1, total: "12.00"),
@@ -110,6 +113,7 @@ extension MockPointOfSaleOrderListService {
110113
status: .refunded,
111114
total: "12.00",
112115
customerEmail: "[email protected]",
116+
paymentMethodID: "cod",
113117
paymentMethodTitle: "Card",
114118
lineItems: [
115119
POSOrderItem(itemID: 6, name: "Cookies", quantity: 1, total: "12.00")

0 commit comments

Comments
 (0)