patx/projectpay

{% extends "base.html" %}
{% block title %}{{ "Edit" if mode == "edit" else "New" }} Project{% endblock %}
{% block page_class %}has-sticky-actions{% endblock %}
{% block content %}
  <div class="header-row">
    <div>
      <h1>{{ "Edit" if mode == "edit" else "New" }} Project</h1>
      {% if mode == "edit" %}
        <p class="muted">{{ project.project_number }}</p>
      {% endif %}
    </div>
  </div>

  <section class="section" style="margin-top:10px;">
    {% if project.errors %}
      <div class="alert error">
        {% for error in project.errors %}
          <div>{{ error }}</div>
        {% endfor %}
      </div>
    {% endif %}

    <form method="post" action="{{ '/edit/' ~ project.id if mode == 'edit' else '/create' }}" id="project-form">
      <div class="grid-2">
        <div class="field">
          <label for="customer_name">Customer Name</label>
          <input id="customer_name" name="customer_name" value="{{ project.customer_name }}" required>
        </div>
        <div class="field">
          <label for="customer_email">Customer Email</label>
          <input id="customer_email" name="customer_email" type="email" value="{{ project.customer_email }}">
        </div>
      </div>

      <h2>Line Items</h2>
      <div id="line-items">
        {% set single_line_item = project.line_items|length <= 1 %}
        {% for item in project.line_items %}
          <div class="line-item">
            <div class="line-item-text">
              <label>Description</label>
              <textarea name="item_name" rows="5" required>{{ item.name }}</textarea>
            </div>
            <div class="line-item-price">
              <label>Price</label>
              <input name="item_price" inputmode="decimal" value="{{ item.price_input if item.price_input is defined else '%.2f'|format((item.amount_cents or 0) / 100) }}" required>
            </div>
            <button class="secondary danger" type="button" data-remove-line {% if single_line_item %}hidden{% endif %}>Remove</button>
          </div>
        {% endfor %}
      </div>

      <button class="secondary add-line-button" type="button" id="add-line">Add Another</button>

      <div class="stats">
        <div class="stat">
          <span>Total</span>
          <strong id="form-total">{{ 0|money(currency) }}</strong>
        </div>
      </div>

      <div class="field">
        <label for="scope_terms">Scope / Terms</label>
        <textarea id="scope_terms" name="scope_terms" rows="7">{{ project.scope_terms }}</textarea>
      </div>

      <div class="sticky-actions">
        <div class="sticky-actions-inner">
          {% if mode == "edit" %}
            <a class="button secondary" href="/project/{{ project.id }}">Back</a>
          {% else %}
            <a class="button secondary" href="/">Cancel</a>
          {% endif %}
          <button type="submit">{{ "Save Project" if mode == "edit" else "Create Project" }}</button>
        </div>
      </div>
    </form>
  </section>

  <script>
    const lineItems = document.getElementById("line-items");
    const addLine = document.getElementById("add-line");
    const total = document.getElementById("form-total");
    const currency = "{{ currency }}";

    function money(cents) {
      return `${currency} ${(cents / 100).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}`;
    }

    function recalc() {
      let cents = 0;
      lineItems.querySelectorAll('input[name="item_price"]').forEach((input) => {
        const parsed = Number(String(input.value).replace(/[$,]/g, ""));
        if (!Number.isNaN(parsed)) cents += Math.round(parsed * 100);
      });
      total.textContent = money(cents);
    }

    function bindRemove(button) {
      button.addEventListener("click", () => {
        if (lineItems.querySelectorAll(".line-item").length <= 1) return;
        button.closest(".line-item").remove();
        updateRemoveButtons();
        recalc();
      });
    }

    function updateRemoveButtons() {
      const rows = lineItems.querySelectorAll(".line-item");
      rows.forEach((row) => {
        const button = row.querySelector("[data-remove-line]");
        button.hidden = rows.length <= 1;
      });
    }

    addLine.addEventListener("click", () => {
      const row = lineItems.querySelector(".line-item").cloneNode(true);
      row.querySelectorAll("input, textarea").forEach((input) => input.value = "");
      bindRemove(row.querySelector("[data-remove-line]"));
      row.querySelectorAll("input").forEach((input) => input.addEventListener("input", recalc));
      lineItems.appendChild(row);
      row.querySelector("textarea").focus();
      updateRemoveButtons();
      recalc();
    });

    lineItems.querySelectorAll("[data-remove-line]").forEach(bindRemove);
    lineItems.querySelectorAll("input").forEach((input) => input.addEventListener("input", recalc));
    updateRemoveButtons();
    recalc();
  </script>
{% endblock %}