mirror of
https://github.com/we-promise/sure.git
synced 2026-05-31 08:19:03 +00:00
157 lines
8.0 KiB
Plaintext
157 lines
8.0 KiB
Plaintext
<div class="space-y-4 pb-6 lg:pb-12">
|
|
<div class="text-xs">
|
|
<%= link_to savings_goals_path, class: "inline-flex items-center gap-1 text-secondary hover:text-primary" do %>
|
|
<%= icon("arrow-left", size: "sm") %>
|
|
<%= t(".back_to_all") %>
|
|
<% end %>
|
|
</div>
|
|
|
|
<header class="flex items-start gap-4">
|
|
<%= render Savings::GoalAvatarComponent.new(goal: @savings_goal, size: "xl") %>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<h1 class="text-2xl font-semibold text-primary truncate"><%= @savings_goal.name %></h1>
|
|
<%= render Savings::StatusPillComponent.new(goal: @savings_goal) %>
|
|
</div>
|
|
<p class="text-sm text-secondary">
|
|
<% if @savings_goal.target_date %>
|
|
<%= t(".header.target_by", amount: @savings_goal.target_amount_money.format, date: I18n.l(@savings_goal.target_date, format: :long)) %>
|
|
<% days = (@savings_goal.target_date - Date.current).to_i %>
|
|
<% if days > 0 %>
|
|
· <%= t("savings_goals.goal_card.days_left", count: days, date: I18n.l(@savings_goal.target_date, format: :long)).split(" · ").first %>
|
|
<% end %>
|
|
<% else %>
|
|
<%= t(".header.target", amount: @savings_goal.target_amount_money.format) %>
|
|
<% end %>
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<%= render DS::Link.new(
|
|
text: t(".edit"),
|
|
variant: "outline",
|
|
href: edit_savings_goal_path(@savings_goal),
|
|
icon: "pencil",
|
|
frame: :modal
|
|
) %>
|
|
<%= render DS::Link.new(
|
|
text: t(".add_contribution"),
|
|
variant: "primary",
|
|
href: new_savings_goal_contribution_path(@savings_goal),
|
|
icon: "plus",
|
|
frame: :modal
|
|
) %>
|
|
<%= render DS::Menu.new do |menu| %>
|
|
<% if @savings_goal.may_pause? %>
|
|
<% menu.with_item(variant: "button", text: t(".pause"), icon: "pause", href: pause_savings_goal_path(@savings_goal), method: :patch) %>
|
|
<% end %>
|
|
<% if @savings_goal.may_resume? %>
|
|
<% menu.with_item(variant: "button", text: t(".resume"), icon: "play", href: resume_savings_goal_path(@savings_goal), method: :patch) %>
|
|
<% end %>
|
|
<% if @savings_goal.may_complete? %>
|
|
<% menu.with_item(variant: "button", text: t(".complete"), icon: "circle-check-big", href: complete_savings_goal_path(@savings_goal), method: :patch) %>
|
|
<% end %>
|
|
<% if @savings_goal.may_archive? %>
|
|
<% menu.with_item(variant: "button", text: t(".archive"), icon: "archive", href: archive_savings_goal_path(@savings_goal), method: :patch) %>
|
|
<% end %>
|
|
<% if @savings_goal.may_unarchive? %>
|
|
<% menu.with_item(variant: "button", text: t(".unarchive"), icon: "archive-restore", href: unarchive_savings_goal_path(@savings_goal), method: :patch) %>
|
|
<% end %>
|
|
<% if @savings_goal.archived? %>
|
|
<% menu.with_item(
|
|
variant: "button",
|
|
text: t(".delete"),
|
|
icon: "trash-2",
|
|
href: savings_goal_path(@savings_goal),
|
|
method: :delete,
|
|
destructive: true,
|
|
confirm: CustomConfirm.for_resource_deletion(@savings_goal.name, high_severity: true)
|
|
) %>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
</header>
|
|
|
|
<%# Top row: ring card + projection chart card %>
|
|
<section class="grid grid-cols-1 lg:grid-cols-[320px_minmax(0,1fr)] gap-3">
|
|
<div class="bg-container rounded-xl shadow-border-xs p-5 flex flex-col items-center justify-center text-center">
|
|
<%= render Savings::ProgressRingComponent.new(goal: @savings_goal, size: 180) %>
|
|
<p class="text-xl font-medium text-primary tabular-nums privacy-sensitive mt-4"><%= @savings_goal.current_balance_money.format %></p>
|
|
<p class="text-xs text-subdued tabular-nums mt-0.5">
|
|
<%= t(".ring.of", target: @savings_goal.target_amount_money.format) %>
|
|
<% unless @savings_goal.completed? %>
|
|
· <%= t(".ring.to_go", amount: @savings_goal.remaining_amount_money.format) %>
|
|
<% end %>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-container rounded-xl shadow-border-xs p-5 flex flex-col">
|
|
<div class="flex items-start justify-between mb-2 gap-3">
|
|
<div class="min-w-0">
|
|
<h3 class="text-sm font-medium text-primary"><%= t(".projection.heading") %></h3>
|
|
<p class="text-xs text-secondary mt-0.5"><%= @stats[:projection_summary].html_safe %></p>
|
|
</div>
|
|
<div class="flex items-center gap-3 text-[11px] text-secondary shrink-0">
|
|
<span class="inline-flex items-center gap-1.5">
|
|
<svg width="18" height="6"><line x1="0" y1="3" x2="18" y2="3" stroke="var(--text-primary)" stroke-width="2" /></svg>
|
|
<%= t(".projection.legend_saved") %>
|
|
</span>
|
|
<span class="inline-flex items-center gap-1.5">
|
|
<svg width="18" height="6"><line x1="0" y1="3" x2="18" y2="3" stroke="var(--color-yellow-600)" stroke-width="2" stroke-dasharray="3 3" /></svg>
|
|
<%= t(".projection.legend_projection") %>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 min-h-[200px]"
|
|
data-controller="savings-goal-projection-chart"
|
|
data-savings-goal-projection-chart-data-value="<%= @savings_goal.projection_payload.to_json %>"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<%# Stat row %>
|
|
<section class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
<div class="bg-container rounded-xl shadow-border-xs px-5 py-4">
|
|
<p class="text-[11px] text-secondary mb-1"><%= t(".stats.avg_monthly") %></p>
|
|
<p class="text-lg font-medium text-primary tabular-nums"><%= Money.new(@stats[:avg_monthly], @savings_goal.currency).format %></p>
|
|
<p class="text-[11px] text-subdued mt-1"><%= @stats[:avg_monthly_sub] %></p>
|
|
</div>
|
|
<div class="bg-container rounded-xl shadow-border-xs px-5 py-4">
|
|
<p class="text-[11px] text-secondary mb-1"><%= t(".stats.total_contributions") %></p>
|
|
<p class="text-lg font-medium text-primary tabular-nums"><%= @stats[:contributions_count] %></p>
|
|
<p class="text-[11px] text-subdued mt-1"><%= t(".stats.across_all_accounts") %></p>
|
|
</div>
|
|
<div class="bg-container rounded-xl shadow-border-xs px-5 py-4">
|
|
<p class="text-[11px] text-secondary mb-1"><%= t(".stats.linked_balance") %></p>
|
|
<p class="text-lg font-medium text-primary tabular-nums"><%= Money.new(@stats[:linked_balance], @savings_goal.currency).format %></p>
|
|
<p class="text-[11px] text-subdued mt-1"><%= @stats[:linked_balance_sub] %></p>
|
|
</div>
|
|
<div class="bg-container rounded-xl shadow-border-xs px-5 py-4">
|
|
<p class="text-[11px] text-secondary mb-1"><%= t(".stats.started") %></p>
|
|
<p class="text-lg font-medium text-primary"><%= I18n.l(@savings_goal.created_at.to_date, format: :long) %></p>
|
|
<p class="text-[11px] text-subdued mt-1"><%= @stats[:started_sub] %></p>
|
|
</div>
|
|
</section>
|
|
|
|
<%# Bottom row: contributions + funding accounts %>
|
|
<section class="grid grid-cols-1 lg:grid-cols-[minmax(0,1.6fr)_minmax(0,1fr)] gap-3">
|
|
<div class="bg-container rounded-xl shadow-border-xs overflow-hidden">
|
|
<div class="flex items-center px-5 py-3.5 border-b border-subdued">
|
|
<h3 class="text-sm font-medium text-primary"><%= t(".contributions_heading") %></h3>
|
|
<span class="ml-2 text-xs text-subdued tabular-nums"><%= @contributions.size %></span>
|
|
</div>
|
|
<%= render "contributions_list", contributions: @contributions %>
|
|
</div>
|
|
|
|
<div class="bg-container rounded-xl shadow-border-xs p-5">
|
|
<h3 class="text-sm font-medium text-primary mb-3"><%= t(".funding_accounts_heading") %></h3>
|
|
<%= render Savings::FundingAccountsBreakdownComponent.new(goal: @savings_goal, rows: @funding_breakdown) %>
|
|
</div>
|
|
</section>
|
|
|
|
<% if @savings_goal.notes.present? %>
|
|
<section class="bg-container rounded-xl shadow-border-xs p-5">
|
|
<h3 class="text-sm font-medium text-primary mb-2"><%= t(".notes") %></h3>
|
|
<p class="text-sm text-secondary whitespace-pre-line"><%= @savings_goal.notes %></p>
|
|
</section>
|
|
<% end %>
|
|
</div>
|