1.4.1 (build 42): contribución por snapshot, fixes Period vs Period y navegación iPad
- 151: contribución editable por snapshot en cualquier modo (quitado gate detailed) con diálogo de propagación al editar: solo este / adelante / atrás / todos (SnapshotRepository.propagateContribution, SnapshotFormViewModel.contributionChanged) - 148: Period vs Period mostraba mal el último mes del period B — la agrupación usaba chartMonth(for:) que aplicaba el grace-period del check-in a snapshots históricos; ahora agrupa por mes calendario crudo - 152: iPad Sources no saltaba entre fuentes — añadido .id() al SourceDetailView para recrear el StateObject al cambiar de selección - 146/147: Year vs Year con forecast del año en curso (asterisco de estimado) y KPIs arriba / detalle debajo - 145: card de contribución mensual en SourceDetailView - Nuevas claves snapshot_contribution_propagate_* en los 7 idiomas Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
This commit is contained in:
@@ -96,6 +96,13 @@ struct DrawdownChart: View {
|
||||
)
|
||||
}
|
||||
.padding(.top, 8)
|
||||
ChartDataTable(
|
||||
rows: drawdownTableRows,
|
||||
valueHeader: "Drawdown",
|
||||
deltaPrevHeader: "Δ prev",
|
||||
deltaFirstHeader: "Δ first"
|
||||
)
|
||||
.padding(.top, 4)
|
||||
} else {
|
||||
Text("Not enough data for drawdown analysis")
|
||||
.foregroundColor(.secondary)
|
||||
@@ -114,6 +121,24 @@ struct DrawdownChart: View {
|
||||
let sum = data.reduce(0.0) { $0 + abs($1.drawdown) }
|
||||
return sum / Double(data.count)
|
||||
}
|
||||
|
||||
private var drawdownTableRows: [ChartDataTableRow] {
|
||||
data.enumerated().map { idx, point in
|
||||
let val = abs(point.drawdown)
|
||||
let prevVal = idx > 0 ? abs(data[idx - 1].drawdown) : val
|
||||
let firstVal = abs(data.first?.drawdown ?? val)
|
||||
let deltaPrev = -(val - prevVal) // negative delta = improvement (less drawdown)
|
||||
let deltaFirst = -(val - firstVal)
|
||||
return ChartDataTableRow(
|
||||
label: chartTableDateLabel(point.date),
|
||||
value: String(format: "%.1f%%", val),
|
||||
deltaPrev: idx > 0 ? String(format: "%+.1f%%", deltaPrev) : nil,
|
||||
deltaFirst: idx > 0 ? String(format: "%+.1f%%", deltaFirst) : nil,
|
||||
isPrevPositive: deltaPrev >= 0,
|
||||
isFirstPositive: deltaFirst >= 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DrawdownStatView: View {
|
||||
@@ -156,15 +181,6 @@ struct VolatilityChartView: View {
|
||||
.font(.headline)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Text("Current")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text(String(format: "%.1f%%", currentVolatility))
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundColor(volatilityColor(currentVolatility))
|
||||
}
|
||||
}
|
||||
|
||||
Text("Measures price variability over time")
|
||||
@@ -221,13 +237,19 @@ struct VolatilityChartView: View {
|
||||
}
|
||||
.frame(height: 250)
|
||||
|
||||
// Volatility interpretation
|
||||
HStack(spacing: 16) {
|
||||
VolatilityLevelView(level: "Low", range: "0-10%", color: .positiveGreen)
|
||||
VolatilityLevelView(level: "Medium", range: "10-20%", color: .appWarning)
|
||||
VolatilityLevelView(level: "High", range: "20%+", color: .negativeRed)
|
||||
}
|
||||
ChartStatsRow(stats: [
|
||||
ChartStat(label: "Current", value: String(format: "%.1f%%", currentVolatility), color: volatilityColor(currentVolatility)),
|
||||
ChartStat(label: "Max", value: String(format: "%.1f%%", data.map { $0.volatility }.max() ?? 0), color: .negativeRed),
|
||||
ChartStat(label: "Min", value: String(format: "%.1f%%", data.map { $0.volatility }.min() ?? 0), color: .positiveGreen),
|
||||
ChartStat(label: "Average", value: String(format: "%.1f%%", averageVolatility), color: .secondary),
|
||||
])
|
||||
.padding(.top, 8)
|
||||
ChartDataTable(
|
||||
rows: volatilityTableRows,
|
||||
valueHeader: "Volatility",
|
||||
deltaPrevHeader: "Δ prev",
|
||||
deltaFirstHeader: nil
|
||||
)
|
||||
} else {
|
||||
Text("Not enough data for volatility analysis")
|
||||
.foregroundColor(.secondary)
|
||||
@@ -251,6 +273,21 @@ struct VolatilityChartView: View {
|
||||
return .negativeRed
|
||||
}
|
||||
}
|
||||
|
||||
private var volatilityTableRows: [ChartDataTableRow] {
|
||||
data.enumerated().map { idx, point in
|
||||
let prevVal = idx > 0 ? data[idx - 1].volatility : point.volatility
|
||||
let delta = point.volatility - prevVal
|
||||
return ChartDataTableRow(
|
||||
label: chartTableDateLabel(point.date),
|
||||
value: String(format: "%.1f%%", point.volatility),
|
||||
deltaPrev: idx > 0 ? String(format: "%+.1f%%", delta) : nil,
|
||||
deltaFirst: nil,
|
||||
isPrevPositive: delta <= 0,
|
||||
isFirstPositive: false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VolatilityLevelView: View {
|
||||
|
||||
Reference in New Issue
Block a user