Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement export group report support #99

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
7 changes: 7 additions & 0 deletions Data/Data/Extension/Date+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ public extension Date {
return dateFormatter.string(from: self)
}

// 13-12-2001
var numericDate: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
return dateFormatter.string(from: self)
}

var millisecondsSince1970: Int {
Int((self.timeIntervalSince1970 * 1000.0).rounded())
}
Expand Down
24 changes: 7 additions & 17 deletions Data/Data/Repository/CommentRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ public class CommentRepository {
@Inject private var activityLogRepository: ActivityLogRepository

public func addComment(group: Groups, expense: Expense? = nil, transaction: Transactions? = nil,
members: (payer: AppUser, receiver: AppUser)? = nil, comment: Comment,
existingCommenterIds: [String]) async throws -> Comment? {
comment: Comment, existingCommenterIds: [String]) async throws -> Comment? {
guard let groupId = group.id else { return nil }

let addedComment = try await store.addComment(groupId: groupId,
Expand All @@ -24,14 +23,13 @@ public class CommentRepository {

try await addActivityLogsInParallel(group: group, expense: expense,
transaction: transaction, comment: comment.comment,
existingCommenterIds: existingCommenterIds, members: members)
existingCommenterIds: existingCommenterIds)
return addedComment
}

/// Add activity logs for all involved users asynchronously and in parallel
private func addActivityLogsInParallel(group: Groups, expense: Expense? = nil, transaction: Transactions? = nil,
comment: String, existingCommenterIds: [String],
members: (payer: AppUser, receiver: AppUser)? = nil) async throws {
comment: String, existingCommenterIds: [String]) async throws {
guard let user = preference.user else { return }

var errors: [Error] = []
Expand All @@ -43,7 +41,7 @@ public class CommentRepository {
taskGroup.addTask { [weak self] in
guard let self else { return nil }
let context = createActivityLogContext(expense: expense, transaction: transaction, group: group,
comment: comment, memberId: memberId, members: members)
comment: comment, memberId: memberId)
return await self.addActivityLog(context: context)
}
}
Expand Down Expand Up @@ -82,25 +80,17 @@ public class CommentRepository {

/// Create ActivityLogContext for expense or transaction
private func createActivityLogContext(expense: Expense? = nil, transaction: Transactions? = nil,
group: Groups, comment: String, memberId: String,
members: (payer: AppUser, receiver: AppUser)? = nil) -> ActivityLogContext? {
group: Groups, comment: String, memberId: String) -> ActivityLogContext? {
guard let user = preference.user else { return nil }
var context: ActivityLogContext?

if let expense {
context = ActivityLogContext(group: group, expense: expense, comment: comment,
type: .expenseCommentAdded, memberId: memberId, currentUser: user)
} else if let transaction, let members {
let payerName = (user.id == transaction.payerId && memberId == transaction.payerId) ?
(user.id == transaction.addedBy ? "You" : "you") :
(memberId == transaction.payerId) ? "you" : members.payer.nameWithLastInitial

let receiverName = (memberId == transaction.receiverId) ? "you" : (memberId == transaction.receiverId) ? "you" : members.receiver.nameWithLastInitial

} else if let transaction {
context = ActivityLogContext(group: group, transaction: transaction, comment: comment,
type: .transactionCommentAdded, memberId: memberId,
currentUser: user, payerName: payerName, receiverName: receiverName,
paymentReason: transaction.reason)
currentUser: user, paymentReason: transaction.reason)
}

return context
Expand Down
4 changes: 4 additions & 0 deletions Data/Data/Repository/ExpenseRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ public class ExpenseRepository: ObservableObject {
return try await store.fetchExpensesBy(groupId: groupId, limit: limit, lastDocument: lastDocument)
}

public func fetchExpenses(groupId: String, startDate: Date?, endDate: Date) async throws -> [Expense] {
try await store.fetchExpenses(groupId: groupId, startDate: startDate, endDate: endDate)
}

public func fetchExpenseBy(groupId: String, expenseId: String) async throws -> Expense {
return try await store.fetchExpenseBy(groupId: groupId, expenseId: expenseId)
}
Expand Down
4 changes: 4 additions & 0 deletions Data/Data/Repository/TransactionRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ public class TransactionRepository: ObservableObject {
try await store.fetchTransactionsBy(groupId: groupId, limit: limit, lastDocument: lastDocument)
}

public func fetchTransactions(groupId: String, startDate: Date?, endDate: Date) async throws -> [Transactions] {
try await store.fetchTransactions(groupId: groupId, startDate: startDate, endDate: endDate)
}

public func fetchTransactionBy(groupId: String, transactionId: String) async throws -> Transactions {
try await store.fetchTransactionsBy(groupId: groupId, transactionId: transactionId)
}
Expand Down
25 changes: 25 additions & 0 deletions Data/Data/Store/ExpenseStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,31 @@ public class ExpenseStore: ObservableObject {
return (expenses, snapshot.documents.last)
}

func fetchExpenses(groupId: String, startDate: Date?, endDate: Date) async throws -> [Expense] {
if let startDate {
let startTimestamp = Timestamp(date: startDate)
let endTimestamp = Timestamp(date: endDate)

let query = expenseReference(groupId: groupId)
.whereField("is_active", isEqualTo: true)
.whereField("date", isGreaterThanOrEqualTo: startTimestamp)
.whereField("date", isLessThanOrEqualTo: endTimestamp)

let snapshot = try await query.getDocuments(source: .server).documents

return try snapshot.map { document in
try document.data(as: Expense.self)
}
} else {
let query = expenseReference(groupId: groupId)
.whereField("is_active", isEqualTo: true)
.order(by: "date", descending: true)
return try await query.getDocuments(source: .server).documents.map {
try $0.data(as: Expense.self)
}
}
}
cp-nirali-s marked this conversation as resolved.
Show resolved Hide resolved

func fetchExpensesOfAllGroups(userId: String, activeGroupIds: [String], limit: Int,
lastDocument: DocumentSnapshot?) async throws -> (expenses: [Expense], lastDocument: DocumentSnapshot?) {

Expand Down
25 changes: 25 additions & 0 deletions Data/Data/Store/TransactionStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,31 @@ public class TransactionStore: ObservableObject {
return (transactions, snapshot.documents.last)
}

func fetchTransactions(groupId: String, startDate: Date?, endDate: Date) async throws -> [Transactions] {
if let startDate {
let startTimestamp = Timestamp(date: startDate)
let endTimestamp = Timestamp(date: endDate)

let query = transactionReference(groupId: groupId)
.whereField("is_active", isEqualTo: true)
.whereField("date", isGreaterThanOrEqualTo: startTimestamp)
.whereField("date", isLessThanOrEqualTo: endTimestamp)

let snapshot = try await query.getDocuments(source: .server).documents

return try snapshot.map { document in
try document.data(as: Transactions.self)
}
} else {
let query = transactionReference(groupId: groupId)
.whereField("is_active", isEqualTo: true)
.order(by: "date", descending: true)
return try await query.getDocuments(source: .server).documents.map {
try $0.data(as: Transactions.self)
}
}
}

func fetchTransactionsBy(groupId: String, transactionId: String) async throws -> Transactions {
try await transactionReference(groupId: groupId)
.document(transactionId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,7 @@ class GroupTransactionDetailViewModel: BaseViewModel, ObservableObject {
}

private func addComment() {
guard let transaction, let group, let transactionId = transaction.id, let userId = preference.user?.id,
let payer = transactionUsersData.first(where: { $0.id == transaction.payerId }),
let receiver = transactionUsersData.first(where: { $0.id == transaction.receiverId }) else {
guard let transaction, let group, let transactionId = transaction.id, let userId = preference.user?.id else {
LogE("GroupTransactionDetailViewModel: \(#function) Missing required data for adding comment.")
return
}
Expand All @@ -212,8 +210,7 @@ class GroupTransactionDetailViewModel: BaseViewModel, ObservableObject {
let comment = Comment(parentId: transactionId,
comment: self.comment.trimming(spaces: .leadingAndTrailing),
commentedBy: userId)
let addedComment = try await self.commentRepository.addComment(group: group, transaction: transaction,
members: (payer, receiver), comment: comment,
let addedComment = try await self.commentRepository.addComment(group: group, transaction: transaction, comment: comment,
existingCommenterIds: self.comments.map { $0.commentedBy })

if let addedComment {
Expand Down
10 changes: 6 additions & 4 deletions Splito/UI/Home/Groups/Group/GroupExpenseListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ struct GroupExpenseListView: View {
GeometryReader { geometry in
ScrollViewReader { scrollProxy in
VStack(alignment: .leading, spacing: 0) {
GroupOptionsListView(isSettleUpEnable: (!viewModel.memberOwingAmount.isEmpty && viewModel.group?.members.count ?? 1 > 1),
onSettleUpTap: viewModel.handleSettleUpBtnTap,
GroupOptionsListView(showExportOptions: $viewModel.showExportOptions,
showShareReportSheet: $viewModel.showShareReportSheet, groupReportUrl: viewModel.groupReportUrl,
isSettleUpEnable: (!viewModel.memberOwingAmount.isEmpty && viewModel.group?.members.count ?? 1 > 1),
onExportTap: viewModel.handleExportBtnTap, onTotalsTap: viewModel.handleTotalBtnTap,
onBalanceTap: viewModel.handleBalancesBtnTap, onSettleUpTap: viewModel.handleSettleUpBtnTap,
onTransactionsTap: viewModel.handleTransactionsBtnTap,
onBalanceTap: viewModel.handleBalancesBtnTap,
onTotalsTap: viewModel.handleTotalBtnTap)
handleExportOptionSelection: viewModel.handleExportOptionSelection(option:))

if viewModel.showSearchBar {
SearchBar(text: $viewModel.searchedExpense, isFocused: isFocused, placeholder: "Search expenses")
Expand Down
51 changes: 48 additions & 3 deletions Splito/UI/Home/Groups/Group/GroupHomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,18 @@ struct GroupHomeView: View {

struct GroupOptionsListView: View {

var isSettleUpEnable: Bool
@Binding var showExportOptions: Bool
@Binding var showShareReportSheet: Bool

let groupReportUrl: URL?
let isSettleUpEnable: Bool

let onExportTap: () -> Void
let onTotalsTap: () -> Void
let onBalanceTap: () -> Void
let onSettleUpTap: () -> Void
let onTransactionsTap: () -> Void
let onBalanceTap: () -> Void
let onTotalsTap: () -> Void
let handleExportOptionSelection: (_ option: ExportOptions) -> Void

var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
Expand All @@ -144,10 +150,49 @@ struct GroupOptionsListView: View {
GroupOptionsButtonView(text: "Balances", onTap: onBalanceTap)

GroupOptionsButtonView(text: "Totals", onTap: onTotalsTap)

GroupOptionsButtonView(text: "Export", onTap: onExportTap)
.confirmationDialog("", isPresented: $showExportOptions, titleVisibility: .hidden) {
ForEach(ExportOptions.allCases, id: \.self) { option in
Button(option.option.localized) {
showExportOptions = false
handleExportOptionSelection(option)
}
}
}
}
.padding([.horizontal, .bottom], 16)
.padding(.top, 24)
}
.sheet(isPresented: $showShareReportSheet) {
if let reportUrl = groupReportUrl {
ShareSheetView(activityItems: [reportUrl]) { isCompleted in
if isCompleted {
showShareReportSheet = false
}
}
}
}
}
}

enum ExportOptions: Int, CaseIterable {

case month, threeMonths, sixMonths, year, all

var option: String {
switch self {
case .month:
return "Month"
case .threeMonths:
return "Three months"
case .sixMonths:
return "Six months"
case .year:
return "Year"
case .all:
return "All"
}
}
}

Expand Down
16 changes: 10 additions & 6 deletions Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,40 @@ import FirebaseFirestore
class GroupHomeViewModel: BaseViewModel, ObservableObject {

private let EXPENSES_LIMIT = 10
let GROUP_REPORT_FILE_NAME = "Splito group report \(Date().shortDate).csv"

@Inject var preference: SplitoPreference
@Inject var groupRepository: GroupRepository
@Inject var expenseRepository: ExpenseRepository
@Inject private var transactionRepository: TransactionRepository
@Inject var transactionRepository: TransactionRepository
cp-nirali-s marked this conversation as resolved.
Show resolved Hide resolved

@Published var group: Groups?
@Published var groupReportUrl: URL?
@Published private(set) var groupId: String

@Published var transactionsCount: Int = 0
@Published private(set) var overallOwingAmount: Double = 0.0
@Published private(set) var currentMonthSpending: Double = 0.0

@Published var group: Groups?
@Published var groupState: GroupState = .loading

@Published var expenses: [Expense] = []
@Published var expensesWithUser: [ExpenseWithUser] = []
@Published var transactionsCount: Int = 0
@Published private(set) var memberOwingAmount: [String: Double] = [:]
@Published private(set) var groupExpenses: [String: [ExpenseWithUser]] = [:]

@Published var showSearchBar = false
@Published var showSettleUpSheet = false
@Published var showBalancesSheet = false
@Published var showExportOptions = false
@Published var showScrollToTopBtn = false
@Published var showGroupTotalSheet = false
@Published var showAddExpenseSheet = false
@Published var showShareReportSheet = false
@Published var showTransactionsSheet = false
@Published var showSimplifyInfoSheet = false
@Published var showInviteMemberSheet = false

@Published var showSearchBar = false
@Published var showScrollToTopBtn = false

@Published var searchedExpense: String = "" {
didSet {
updateGroupExpenses()
Expand Down
Loading
Loading