added preview and update some views

This commit is contained in:
wt committed 2026-09-08 00:59:28 +07:00
1 parent a11510d6fa
commit 2ceacb9c2b
22 files changed
+1267 -172

No files matched your search

+5
View File
@@ -38,3 +38,8 @@ struct ContentView: View {
}
}
}
#Preview("Главный экран") {
ContentView()
.modelContainer(BalanceModelContainer.previewContainer)
}
@@ -56,3 +56,101 @@ enum BalanceModelContainer {
}
}
}
// MARK: - Превью
extension BalanceModelContainer {
/// Контейнер в памяти с демо-данными для #Preview.
@MainActor
static var previewContainer: ModelContainer {
let configuration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: true,
cloudKitDatabase: .none
)
do {
let container = try ModelContainer(for: schema, configurations: [configuration])
PreviewSampleData.seed(into: container.mainContext)
return container
} catch {
fatalError("Не удалось создать контейнер для превью: \(error.localizedDescription)")
}
}
}
/// Демо-данные, которыми наполняется контейнер превью.
@MainActor
private enum PreviewSampleData {
static func seed(into context: ModelContext) {
let calendar = Calendar.current
let monthStart = FinanceCalculations.monthInterval(containing: .now, calendar: calendar).start
context.insert(CustomCategory(
name: "Кофе",
icon: "cup.and.saucer.fill",
emoji: "☕️",
colorName: "brown",
kind: .expense
))
let expenses: [(name: String, icon: String, emoji: String, colorName: String, amount: Double)] = [
("Продукты", "cart.fill", "", "auto", 3_400),
("Транспорт", "car.fill", "", "auto", 1_200),
("Развлечения", "gamecontroller.fill", "", "auto", 2_600),
("Подписки", "repeat", "", "auto", 649),
("Кофе", "cup.and.saucer.fill", "☕️", "brown", 380),
("Покупки", "bag.fill", "", "auto", 4_800)
]
for monthOffset in 0..<6 {
guard let anchor = calendar.date(byAdding: .month, value: -monthOffset, to: monthStart) else {
continue
}
if let salaryDate = calendar.date(byAdding: .day, value: 2, to: anchor) {
context.insert(FinanceTransaction(
amount: 150_000,
date: salaryDate,
note: "",
categoryName: "Зарплата",
categoryIcon: "banknote.fill",
kind: .income
))
}
for (index, template) in expenses.enumerated() {
guard let date = calendar.date(byAdding: .day, value: 4 + index * 3, to: anchor) else {
continue
}
let variation = 1 + Double((monthOffset + index) % 4) * 0.08
context.insert(FinanceTransaction(
amount: (template.amount * variation).rounded(),
date: date,
note: "",
categoryName: template.name,
categoryIcon: template.icon,
categoryEmoji: template.emoji,
categoryColorName: template.colorName,
kind: .expense
))
}
}
let budgets: [(name: String, icon: String, emoji: String, limit: Double)] = [
("Продукты", "cart.fill", "", 30_000),
("Транспорт", "car.fill", "", 8_000),
("Развлечения", "gamecontroller.fill", "", 10_000),
("Кофе", "cup.and.saucer.fill", "☕️", 5_000)
]
for budget in budgets {
context.insert(MonthlyBudget(
categoryName: budget.name,
categoryIcon: budget.icon,
categoryEmoji: budget.emoji,
limit: budget.limit,
monthStart: monthStart
))
}
}
}
File diff suppressed because it is too large. Load diff
@@ -334,6 +334,11 @@ struct AnalyticsView: View {
}
}
#Preview("Аналитика") {
AnalyticsView()
.modelContainer(BalanceModelContainer.previewContainer)
}
private struct AnalyticsMetricCard: View {
let title: String
let value: String
@@ -355,3 +360,21 @@ private struct AnalyticsMetricCard: View {
.background(.background, in: RoundedRectangle(cornerRadius: 18, style: .continuous))
}
}
#Preview("Карточка метрики") {
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) {
AnalyticsMetricCard(
title: "Доходы",
value: "150 000 ₽",
icon: "arrow.down.left",
tint: .green
)
AnalyticsMetricCard(
title: "Расходы",
value: "87 300 ₽",
icon: "arrow.up.right",
tint: .red
)
}
.padding()
}
@@ -111,3 +111,24 @@ struct BudgetEditorView: View {
dismiss()
}
}
#Preview("Лимиты") {
BudgetEditorView(
budgets: [
MonthlyBudget(
categoryName: "Продукты",
categoryIcon: "cart.fill",
limit: 30_000,
monthStart: .now
),
MonthlyBudget(
categoryName: "Транспорт",
categoryIcon: "car.fill",
limit: 8_000,
monthStart: .now
)
],
monthStart: FinanceCalculations.monthInterval(containing: .now).start
)
.modelContainer(BalanceModelContainer.previewContainer)
}
@@ -116,3 +116,8 @@ struct BudgetsView: View {
return .indigo
}
}
#Preview("Бюджеты") {
BudgetsView()
.modelContainer(BalanceModelContainer.previewContainer)
}
@@ -33,3 +33,20 @@ struct CategoryIconView: View {
}
}
}
#Preview("Иконки") {
HStack(spacing: 16) {
CategoryIconView(systemName: "cart.fill", emoji: "", tint: .orange, containerSize: 40)
CategoryIconView(systemName: "car.fill", emoji: "", tint: .blue)
CategoryIconView(systemName: "banknote.fill", emoji: "", tint: .green, size: 24, containerSize: 52)
}
.padding()
}
#Preview("Эмодзи") {
HStack(spacing: 16) {
CategoryIconView(systemName: "star.fill", emoji: "☕️", tint: .brown, size: 21, containerSize: 40)
CategoryIconView(systemName: "star.fill", emoji: "🐶", tint: .purple, size: 21, containerSize: 40)
}
.padding()
}
@@ -22,3 +22,12 @@ struct EmptyStateCard: View {
.background(.background, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
}
}
#Preview("Пустое состояние") {
EmptyStateCard(
icon: "tray",
title: "История пуста",
message: "Доходы и расходы будут собраны здесь."
)
.padding()
}
@@ -31,3 +31,23 @@ struct SummaryCard: View {
.shadow(color: .black.opacity(0.05), radius: 12, y: 5)
}
}
#Preview("Карточки сводки") {
HStack(spacing: 12) {
SummaryCard(
title: "Доходы",
amount: 150_000,
currencyCode: "RUB",
icon: "arrow.down.left",
tint: .green
)
SummaryCard(
title: "Расходы",
amount: 87_300,
currencyCode: "RUB",
icon: "arrow.up.right",
tint: .red
)
}
.padding()
}
@@ -105,3 +105,8 @@ struct BalanceAdjustmentView: View {
dismiss()
}
}
#Preview("Корректировка баланса") {
BalanceAdjustmentView()
.modelContainer(BalanceModelContainer.previewContainer)
}
@@ -211,3 +211,8 @@ struct DashboardView: View {
return FinanceCategory.color(named: item.colorName)
}
}
#Preview("Обзор") {
DashboardView()
.modelContainer(BalanceModelContainer.previewContainer)
}
@@ -338,3 +338,28 @@ private struct CategoryEditorView: View {
dismiss()
}
}
#Preview("Категории") {
NavigationStack {
CategoriesView()
}
.modelContainer(BalanceModelContainer.previewContainer)
}
#Preview("Новая категория") {
CategoryEditorView()
.modelContainer(BalanceModelContainer.previewContainer)
}
#Preview("Редактирование категории") {
CategoryEditorView(
category: CustomCategory(
name: "Кофе",
icon: "cup.and.saucer.fill",
emoji: "☕️",
colorName: "brown",
kind: .expense
)
)
.modelContainer(BalanceModelContainer.previewContainer)
}
@@ -84,3 +84,8 @@ struct SettingsView: View {
return "Без входа данные остаются на устройстве. Собственный сервер можно подключить выше без iCloud и платной Apple Developer Team."
}
}
#Preview("Настройки") {
SettingsView()
.modelContainer(BalanceModelContainer.previewContainer)
}
@@ -175,3 +175,22 @@ struct NewTransactionView: View {
return kind == .expense ? "Новый расход" : "Новый доход"
}
}
#Preview("Новая операция") {
NewTransactionView()
.modelContainer(BalanceModelContainer.previewContainer)
}
#Preview("Редактирование операции") {
NewTransactionView(
transaction: FinanceTransaction(
amount: 2_450,
date: .now,
note: "Пятёрочка у дома",
categoryName: "Продукты",
categoryIcon: "cart.fill",
kind: .expense
)
)
.modelContainer(BalanceModelContainer.previewContainer)
}
@@ -44,3 +44,37 @@ struct TransactionRow: View {
return prefix + transaction.amount.formattedCurrency(code: currencyCode)
}
}
#Preview("Расход") {
List {
TransactionRow(
transaction: FinanceTransaction(
amount: 2_450,
date: .now,
note: "Пятёрочка у дома",
categoryName: "Продукты",
categoryIcon: "cart.fill",
kind: .expense
),
currencyCode: "RUB"
)
}
.listStyle(.plain)
}
#Preview("Доход") {
List {
TransactionRow(
transaction: FinanceTransaction(
amount: 150_000,
date: .now,
note: "",
categoryName: "Зарплата",
categoryIcon: "banknote.fill",
kind: .income
),
currencyCode: "RUB"
)
}
.listStyle(.plain)
}
@@ -102,3 +102,8 @@ struct TransactionsView: View {
}
}
}
#Preview("Операции") {
TransactionsView()
.modelContainer(BalanceModelContainer.previewContainer)
}
+54
View File
@@ -434,3 +434,57 @@ private struct MacCategoriesView: View {
}
}
}
#Preview("Главное окно") {
MacContentView()
.modelContainer(BalanceModelContainer.previewContainer)
}
#Preview("Обзор") {
MacOverviewView()
.modelContainer(BalanceModelContainer.previewContainer)
.frame(width: 900, height: 620)
}
#Preview("Карточки метрик") {
HStack(spacing: 14) {
MacMetricCard(
title: "Общий баланс",
value: "62 700 ₽",
icon: "creditcard.fill",
tint: .indigo
)
MacMetricCard(
title: "Доходы за месяц",
value: "150 000 ₽",
icon: "arrow.down.left",
tint: .green
)
MacMetricCard(
title: "Расходы за месяц",
value: "87 300 ₽",
icon: "arrow.up.right",
tint: .red
)
}
.padding()
.frame(width: 720)
}
#Preview("Операции") {
MacTransactionsView()
.modelContainer(BalanceModelContainer.previewContainer)
.frame(width: 900, height: 620)
}
#Preview("Бюджеты") {
MacBudgetsView()
.modelContainer(BalanceModelContainer.previewContainer)
.frame(width: 900, height: 620)
}
#Preview("Категории") {
MacCategoriesView()
.modelContainer(BalanceModelContainer.previewContainer)
.frame(width: 900, height: 620)
}
+37
View File
@@ -107,6 +107,25 @@ struct MacTransactionEditor: View {
}
}
#Preview("Новая операция") {
MacTransactionEditor()
.modelContainer(BalanceModelContainer.previewContainer)
}
#Preview("Редактирование операции") {
MacTransactionEditor(
transaction: FinanceTransaction(
amount: 2_450,
date: .now,
note: "Пятёрочка у дома",
categoryName: "Продукты",
categoryIcon: "cart.fill",
kind: .expense
)
)
.modelContainer(BalanceModelContainer.previewContainer)
}
struct MacCategoryEditor: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@@ -231,3 +250,21 @@ struct MacCategoryEditor: View {
dismiss()
}
}
#Preview("Новая категория") {
MacCategoryEditor()
.modelContainer(BalanceModelContainer.previewContainer)
}
#Preview("Редактирование категории") {
MacCategoryEditor(
category: CustomCategory(
name: "Кофе",
icon: "cup.and.saucer.fill",
emoji: "☕️",
colorName: "brown",
kind: .expense
)
)
.modelContainer(BalanceModelContainer.previewContainer)
}
+40
View File
@@ -174,6 +174,12 @@ struct MacAnalyticsView: View {
}
}
#Preview("Аналитика") {
MacAnalyticsView()
.modelContainer(BalanceModelContainer.previewContainer)
.frame(width: 1000, height: 700)
}
struct MacBalanceAdjustmentView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@@ -229,6 +235,11 @@ struct MacBalanceAdjustmentView: View {
}
}
#Preview("Корректировка баланса") {
MacBalanceAdjustmentView()
.modelContainer(BalanceModelContainer.previewContainer)
}
struct MacBudgetEditor: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@@ -308,6 +319,27 @@ struct MacBudgetEditor: View {
}
}
#Preview("Лимиты") {
MacBudgetEditor(
budgets: [
MonthlyBudget(
categoryName: "Продукты",
categoryIcon: "cart.fill",
limit: 30_000,
monthStart: .now
),
MonthlyBudget(
categoryName: "Транспорт",
categoryIcon: "car.fill",
limit: 8_000,
monthStart: .now
)
],
monthStart: FinanceCalculations.monthInterval(containing: .now).start
)
.modelContainer(BalanceModelContainer.previewContainer)
}
struct MacSettingsView: View {
@Query private var transactions: [FinanceTransaction]
@AppStorage("currencyCode") private var currencyCode = SupportedCurrency.RUB.rawValue
@@ -359,3 +391,11 @@ struct MacSettingsView: View {
.padding(24)
}
}
#Preview("Настройки") {
NavigationStack {
MacSettingsView()
}
.modelContainer(BalanceModelContainer.previewContainer)
.frame(width: 640, height: 640)
}
@@ -108,3 +108,22 @@ struct WatchAddTransactionView: View {
dismiss()
}
}
#Preview("Новая операция") {
WatchAddTransactionView()
.modelContainer(BalanceModelContainer.previewContainer)
}
#Preview("Редактирование операции") {
WatchAddTransactionView(
transaction: FinanceTransaction(
amount: 2_450,
date: .now,
note: "Пятёрочка у дома",
categoryName: "Продукты",
categoryIcon: "cart.fill",
kind: .expense
)
)
.modelContainer(BalanceModelContainer.previewContainer)
}
+90 -1
View File
@@ -99,6 +99,13 @@ struct WatchAnalyticsView: View {
}
}
#Preview("Аналитика") {
NavigationStack {
WatchAnalyticsView()
}
.modelContainer(BalanceModelContainer.previewContainer)
}
struct WatchTransactionsView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \FinanceTransaction.date, order: .reverse)
@@ -169,6 +176,13 @@ struct WatchTransactionsView: View {
}
}
#Preview("Операции") {
NavigationStack {
WatchTransactionsView()
}
.modelContainer(BalanceModelContainer.previewContainer)
}
struct WatchBudgetsView: View {
@Query private var budgets: [MonthlyBudget]
@Query private var transactions: [FinanceTransaction]
@@ -218,6 +232,13 @@ struct WatchBudgetsView: View {
}
}
#Preview("Бюджеты") {
NavigationStack {
WatchBudgetsView()
}
.modelContainer(BalanceModelContainer.previewContainer)
}
private struct WatchBudgetEditor: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@@ -242,7 +263,9 @@ private struct WatchBudgetEditor: View {
var body: some View {
Form {
ForEach(categories) { category in
TextField(category.name, value: binding(category.name), format: .number)
Section(category.name) {
TextField(category.name, value: binding(category.name), format: .number)
}
}
Button("Сохранить", action: save)
}
@@ -278,6 +301,29 @@ private struct WatchBudgetEditor: View {
}
}
#Preview("Лимиты") {
NavigationStack {
WatchBudgetEditor(
budgets: [
MonthlyBudget(
categoryName: "Продукты",
categoryIcon: "cart.fill",
limit: 30_000,
monthStart: .now
),
MonthlyBudget(
categoryName: "Транспорт",
categoryIcon: "car.fill",
limit: 8_000,
monthStart: .now
)
],
monthStart: FinanceCalculations.monthInterval(containing: .now).start
)
}
.modelContainer(BalanceModelContainer.previewContainer)
}
struct WatchCategoriesView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \CustomCategory.createdAt) private var categories: [CustomCategory]
@@ -323,6 +369,13 @@ struct WatchCategoriesView: View {
}
}
#Preview("Категории") {
NavigationStack {
WatchCategoriesView()
}
.modelContainer(BalanceModelContainer.previewContainer)
}
private struct WatchCategoryEditor: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@@ -425,6 +478,28 @@ private struct WatchCategoryEditor: View {
}
}
#Preview("Новая категория") {
NavigationStack {
WatchCategoryEditor()
}
.modelContainer(BalanceModelContainer.previewContainer)
}
#Preview("Редактирование категории") {
NavigationStack {
WatchCategoryEditor(
category: CustomCategory(
name: "Кофе",
icon: "cup.and.saucer.fill",
emoji: "☕️",
colorName: "brown",
kind: .expense
)
)
}
.modelContainer(BalanceModelContainer.previewContainer)
}
struct WatchBalanceAdjustmentView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@@ -465,6 +540,13 @@ struct WatchBalanceAdjustmentView: View {
}
}
#Preview("Корректировка баланса") {
NavigationStack {
WatchBalanceAdjustmentView()
}
.modelContainer(BalanceModelContainer.previewContainer)
}
struct WatchSettingsView: View {
@AppStorage("currencyCode") private var currencyCode = SupportedCurrency.RUB.rawValue
@AppStorage("appTheme") private var appTheme = AppTheme.system.rawValue
@@ -491,3 +573,10 @@ struct WatchSettingsView: View {
.navigationTitle("Настройки")
}
}
#Preview("Настройки") {
NavigationStack {
WatchSettingsView()
}
.modelContainer(BalanceModelContainer.previewContainer)
}
+14 -2
View File
@@ -57,8 +57,9 @@ struct WatchRootView: View {
showingAddTransaction = true
} label: {
Label("Добавить операцию", systemImage: "plus.circle.fill")
.frame(maxWidth: .infinity)
// .frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
VStack(spacing: 4) {
@@ -67,35 +68,41 @@ struct WatchRootView: View {
} label: {
Label("Аналитика", systemImage: "chart.xyaxis.line")
}
.frame(maxWidth: .infinity)
NavigationLink {
WatchTransactionsView()
} label: {
Label("Все операции", systemImage: "list.bullet.rectangle")
}
.frame(maxWidth: .infinity)
NavigationLink {
WatchBudgetsView()
} label: {
Label("Бюджеты", systemImage: "target")
}
.frame(maxWidth: .infinity)
NavigationLink {
WatchCategoriesView()
} label: {
Label("Категории", systemImage: "square.grid.2x2")
}
.frame(maxWidth: .infinity)
NavigationLink {
WatchBalanceAdjustmentView()
} label: {
Label("Корректировка", systemImage: "slider.horizontal.3")
}
.frame(maxWidth: .infinity)
NavigationLink {
WatchSettingsView()
} label: {
Label("Настройки", systemImage: "gearshape")
}
.frame(maxWidth: .infinity)
}
if !transactions.isEmpty {
Text("Последние")
Text("Последние операции")
.font(.headline)
ForEach(transactions.prefix(4)) { transaction in
HStack(spacing: 8) {
@@ -156,3 +163,8 @@ struct WatchRootView: View {
return FinanceCategory.color(named: transaction.categoryColorName)
}
}
#Preview("Обзор") {
WatchRootView()
.modelContainer(BalanceModelContainer.previewContainer)
}