11import SwiftUI
22import struct Yosemite. POSOrder
3+ import enum Yosemite. OrderPaymentMethod
4+ import WooFoundation
35
46struct 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 " )
0 commit comments