diff --git a/public/admin-invoice-form.js b/public/admin-invoice-form.js new file mode 100644 index 0000000..39b2f1c --- /dev/null +++ b/public/admin-invoice-form.js @@ -0,0 +1,28 @@ +(function () { + const tbody = document.querySelector('#items-table tbody'); + const addBtn = document.getElementById('add-row'); + + function reindex() { + Array.from(tbody.querySelectorAll('tr.item-row')).forEach((row, i) => { + row.querySelectorAll('input').forEach((input) => { + input.name = input.name.replace(/items\[\d+\]/, 'items[' + i + ']'); + }); + }); + } + + addBtn.addEventListener('click', function () { + const last = tbody.querySelector('tr.item-row:last-child'); + const clone = last.cloneNode(true); + clone.querySelectorAll('input').forEach((input) => { input.value = ''; }); + tbody.appendChild(clone); + reindex(); + }); + + tbody.addEventListener('click', function (e) { + if (!e.target.matches('.remove-row')) return; + const rows = tbody.querySelectorAll('tr.item-row'); + if (rows.length <= 1) return; + e.target.closest('tr.item-row').remove(); + reindex(); + }); +})(); diff --git a/views/admin/invoices/index.erb b/views/admin/invoices/index.erb new file mode 100644 index 0000000..08241c0 --- /dev/null +++ b/views/admin/invoices/index.erb @@ -0,0 +1,41 @@ +
+

Invoices

+

+ New invoice

+
+ +<% if @invoices.empty? %> +
+

No invoices yet. Create the first one.

+
+<% else %> +
+ + + + + + + + + + + + + <% @invoices.each do |inv| %> + + + + + + + + + <% end %> + +
NumberClientIssuedTotalStatus
<%= inv.number %><%= inv.client_name %><%= inv.issued_on.strftime('%Y-%m-%d') %><%= inv.currency %> <%= '%.2f' % inv.total %> + <%= inv.status %> + + view +
+
+<% end %> diff --git a/views/admin/invoices/new.erb b/views/admin/invoices/new.erb new file mode 100644 index 0000000..217a360 --- /dev/null +++ b/views/admin/invoices/new.erb @@ -0,0 +1,88 @@ +
+

New invoice

+

← all invoices

+
+ +<% if @form_errors && !@form_errors.empty? %> +
+ <% @form_errors.each_value do |msg| %> +
<%= msg %>
+ <% end %> +
+<% end %> + +
+
+ + +

Client

+
+ + +
+
+ + +
+
+ + +
+ +

Terms

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +

Line items

+ + + + + + + + + + + <% (@form_values[:items] || [{'description' => '', 'qty' => '', 'unit_price' => ''}]).each_with_index do |item, i| %> + + + + + + + <% end %> + +
DescriptionQtyUnit price
+ + +
+ + +
+ + +
+
+ + diff --git a/views/admin/invoices/show.erb b/views/admin/invoices/show.erb new file mode 100644 index 0000000..c3883b5 --- /dev/null +++ b/views/admin/invoices/show.erb @@ -0,0 +1,43 @@ +
+

<%= @invoice.number %>

+

← all invoices

+
+ +
+
+
Client
<%= @invoice.client_name %> <<%= @invoice.client_email %>>
+
Issued
<%= @invoice.issued_on.strftime('%Y-%m-%d') %>
+
Due
<%= @invoice.due_on.strftime('%Y-%m-%d') %>
+
Total
<%= @invoice.currency %> <%= '%.2f' % @invoice.total %><% unless @invoice.currency == 'GEL' %> · GEL <%= '%.2f' % @invoice.total_gel %><% end %>
+
Status
<%= @invoice.status %>
+ <% if @invoice.paid_at %> +
Paid at
<%= @invoice.paid_at.utc.strftime('%Y-%m-%d %H:%M UTC') %>
+ <% end %> +
+
+ +
+

Actions

+

+ Public landing page +  ·  + Download PDF +

+

+ Share this URL with the client:
+ https://asxp.io/i/<%= @invoice.uuid %> +

+
+ + +
+
+ +<% unless @invoice.notes.to_s.strip.empty? %> +
+

Notes

+

<%= @invoice.notes %>

+
+<% end %> diff --git a/views/invoice_public.erb b/views/invoice_public.erb new file mode 100644 index 0000000..07f4ae4 --- /dev/null +++ b/views/invoice_public.erb @@ -0,0 +1,24 @@ +
+

Invoice <%= @invoice.number %>

+

from IE Sergei Poljanski

+
+ +
+
+
Billed to
<%= @invoice.client_name %>
+
Issued
<%= @invoice.issued_on.strftime('%Y-%m-%d') %>
+
Due
<%= @invoice.due_on.strftime('%Y-%m-%d') %>
+
Total
<%= @invoice.currency %> <%= '%.2f' % @invoice.total %>
+
Status
<%= @invoice.status %>
+
+
+ +
+

+ Download PDF +

+
+ +