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:
@@ -77,6 +77,28 @@ struct PredictionChartView: View {
|
||||
.symbolSize(24)
|
||||
}
|
||||
|
||||
// Bull scenario line (upper confidence interval)
|
||||
ForEach(predictions) { prediction in
|
||||
LineMark(
|
||||
x: .value("Date", prediction.date),
|
||||
y: .value("Bull", NSDecimalNumber(decimal: prediction.confidenceInterval.upper).doubleValue),
|
||||
series: .value("Scenario", "bull")
|
||||
)
|
||||
.foregroundStyle(Color.positiveGreen.opacity(0.7))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1.5, dash: [4, 3]))
|
||||
}
|
||||
|
||||
// Bear scenario line (lower confidence interval)
|
||||
ForEach(predictions) { prediction in
|
||||
LineMark(
|
||||
x: .value("Date", prediction.date),
|
||||
y: .value("Bear", NSDecimalNumber(decimal: prediction.confidenceInterval.lower).doubleValue),
|
||||
series: .value("Scenario", "bear")
|
||||
)
|
||||
.foregroundStyle(Color.negativeRed.opacity(0.7))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1.5, dash: [4, 3]))
|
||||
}
|
||||
|
||||
// Connect historical to prediction
|
||||
if let lastHistorical = historicalData.last,
|
||||
let firstPrediction = predictions.first {
|
||||
@@ -114,6 +136,17 @@ struct PredictionChartView: View {
|
||||
}
|
||||
.frame(height: 280)
|
||||
|
||||
// Stats row
|
||||
if let currentValue = historicalData.last?.value,
|
||||
let lastPred = predictions.last {
|
||||
ChartStatsRow(stats: [
|
||||
ChartStat(label: "Current", value: currentValue.compactCurrencyString, color: .appPrimary),
|
||||
ChartStat(label: "Base (\(predictions.count)M)", value: lastPred.predictedValue.compactCurrencyString, color: .appSecondary),
|
||||
ChartStat(label: "Bull case", value: lastPred.confidenceInterval.upper.compactCurrencyString, color: .positiveGreen),
|
||||
ChartStat(label: "Bear case", value: lastPred.confidenceInterval.lower.compactCurrencyString, color: .negativeRed),
|
||||
])
|
||||
}
|
||||
|
||||
// Legend
|
||||
HStack(spacing: 20) {
|
||||
HStack(spacing: 6) {
|
||||
@@ -150,6 +183,24 @@ struct PredictionChartView: View {
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
HStack(spacing: 6) {
|
||||
Rectangle()
|
||||
.fill(Color.positiveGreen.opacity(0.7))
|
||||
.frame(width: 20, height: 2)
|
||||
Text("Bull")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
HStack(spacing: 6) {
|
||||
Rectangle()
|
||||
.fill(Color.negativeRed.opacity(0.7))
|
||||
.frame(width: 20, height: 2)
|
||||
Text("Bear")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Prediction details
|
||||
@@ -195,6 +246,14 @@ struct PredictionChartView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider().padding(.vertical, 4)
|
||||
ChartDataTable(
|
||||
rows: predictionTableRows,
|
||||
valueHeader: "Base",
|
||||
deltaPrevHeader: "Bull",
|
||||
deltaFirstHeader: "Bear"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
VStack(spacing: 12) {
|
||||
@@ -220,6 +279,19 @@ struct PredictionChartView: View {
|
||||
.cornerRadius(AppConstants.UI.cornerRadius)
|
||||
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
|
||||
}
|
||||
|
||||
private var predictionTableRows: [ChartDataTableRow] {
|
||||
predictions.map { pred in
|
||||
ChartDataTableRow(
|
||||
label: chartTableDateLabel(pred.date),
|
||||
value: pred.predictedValue.compactCurrencyString,
|
||||
deltaPrev: pred.confidenceInterval.upper.compactCurrencyString,
|
||||
deltaFirst: pred.confidenceInterval.lower.compactCurrencyString,
|
||||
isPrevPositive: true,
|
||||
isFirstPositive: false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
Reference in New Issue
Block a user