Files
vibetree/web/templates/partials/modals.html
T
Owen fb85c9edbb Add asset checkout, quantity/uniqueness, contacts, and QR labels
Checkout:
- assets.AssignUser + POST /app/assets/{id}/checkout assign/unassign an
  asset's user independent of the full edit form. New shared
  #checkoutModal, wired from a "Check Out..." context-menu action, backed
  by GET /app/assets/{id} and GET /app/users/options JSON endpoints.

Quantity / uniqueness:
- Item types gain an IsUnique flag (checked by default, matching prior
  behavior). Assets gain a Quantity column, fixed at 1 for unique types
  and adjustable via +/- steppers (POST /app/assets/{id}/quantity) for
  non-unique ones. Flipping a type back to unique clamps existing assets'
  quantity back to 1. CSV import/export gained a Quantity column.
  Migration 0003_quantity.sql.

Contacts:
- New models.RoleContact: an assignable person who cannot sign in
  (User.CanLogin). Migration 0004_contacts.sql rebuilds the users table
  (SQLite can't alter a CHECK constraint in place) to allow the role.
  Users form/list updated; contact creation auto-generates an unusable
  password since the login path is blocked by role regardless.

QR asset tags and printable labels:
- Each asset's QR code (via github.com/skip2/go-qrcode, pure Go) encodes
  an absolute /scan/<tag> URL; GET /scan/{tag} resolves it and redirects
  into /inventory?asset=<id>, which app.js picks up on load to
  auto-select that asset - works from a phone camera or a USB
  scanner-as-keyboard-wedge pointed at the address bar.
- GET /labels renders a print-ready sheet (right-click an asset for one
  label, a folder for every asset in it) with a template switcher between
  Avery 5160 (verified dimensions) and 61526 asset tag labels (best-effort
  layout, flagged as such on the page).

Also refreshed README to reflect the right-click-first tree UX and all of
the above.
2026-08-17 16:28:14 -05:00

186 lines
8.3 KiB
HTML

{{define "modals"}}
{{if .User.IsAdmin}}
<!-- Create / rename folder -->
<div class="modal fade" id="folderModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form id="folderForm">
<div class="modal-header">
<h5 class="modal-title" id="folderModalTitle">New Folder</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="folder_id" id="folderFormId">
<input type="hidden" name="parent_id" id="folderFormParentId">
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" class="form-control" name="name" id="folderFormName" required autofocus>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" name="description" id="folderFormDescription" rows="2"></textarea>
</div>
<div class="row g-2">
<div class="col">
<label class="form-label">Icon <span class="text-body-secondary">(optional)</span></label>
<div class="icon-picker" data-icon-picker>
<input type="hidden" name="icon" id="folderFormIcon" value="">
<button type="button" class="btn btn-outline-secondary icon-picker-toggle w-100">
<i class="bi icon-picker-preview"></i>
<span class="icon-picker-label text-body-secondary">Choose icon&hellip;</span>
<i class="bi bi-chevron-down icon-picker-caret"></i>
</button>
<div class="icon-picker-menu">
<input type="search" class="form-control form-control-sm icon-picker-search" placeholder="Search icons&hellip;">
<div class="icon-picker-grid"></div>
<button type="button" class="btn btn-sm btn-link p-0 mt-2 icon-picker-clear">Clear</button>
</div>
</div>
</div>
<div class="col-4">
<label class="form-label">Sort Order</label>
<input type="number" class="form-control" name="sort_order" id="folderFormSortOrder" value="0">
</div>
</div>
<div class="invalid-feedback d-block d-none" id="folderFormError"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Folder</button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete folder confirmation -->
<div class="modal fade" id="deleteFolderModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-exclamation-triangle-fill text-danger"></i> Delete Folder</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Delete <strong id="deleteFolderName"></strong>?</p>
<div id="deleteFolderEmptyNotice" class="d-none">
<p class="text-body-secondary">This folder is empty and can be deleted safely.</p>
</div>
<div id="deleteFolderNotEmptyNotice" class="d-none">
<div class="alert alert-warning py-2">
This folder contains <span id="deleteFolderCounts"></span>. Choose what to do with its contents.
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="delete_mode" id="modeRelocate" value="relocate" checked>
<label class="form-check-label" for="modeRelocate">Move contents to&hellip;</label>
</div>
<select class="form-select mt-1 mb-2" id="deleteFolderDestination"></select>
<div class="form-check">
<input class="form-check-input" type="radio" name="delete_mode" id="modeCascade" value="cascade">
<label class="form-check-label text-danger" for="modeCascade">Permanently delete everything inside</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDeleteFolderBtn">Delete Folder</button>
</div>
</div>
</div>
</div>
<!-- Add / edit asset -->
<div class="modal fade" id="assetModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="assetModalTitle">New Asset</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="assetModalBody"></div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="assetForm" class="btn btn-primary" id="assetModalSubmit">Create Asset</button>
</div>
</div>
</div>
</div>
<!-- Move asset -->
<div class="modal fade" id="moveAssetModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Move Asset</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<label class="form-label">Destination Folder</label>
<select class="form-select" id="moveAssetDestination">
<option value="">Inventory (top level)</option>
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirmMoveAssetBtn">Move</button>
</div>
</div>
</div>
</div>
<!-- Check out / check in asset -->
<div class="modal fade" id="checkoutModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="checkoutModalTitle">Check Out Asset</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-body-secondary small mb-3" id="checkoutCurrent"></p>
<label class="form-label">Assigned User</label>
<select class="form-select" id="checkoutUserSelect">
<option value="">Unassigned (check in)</option>
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirmCheckoutBtn">Save</button>
</div>
</div>
</div>
</div>
<!-- Import CSV into a specific folder (right-click a folder > Import CSV...) -->
<div class="modal fade" id="importModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form id="importForm">
<div class="modal-header">
<h5 class="modal-title" id="importModalTitle">Import CSV</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="folder_id" id="importFolderId">
<div class="mb-3">
<label class="form-label">CSV File</label>
<input type="file" class="form-control" name="file" id="importFile" accept=".csv" required>
</div>
<p class="text-body-secondary small mb-0">
Every row imports into this folder. Columns: <code>Name</code>, <code>Item Type</code>,
<code>Status</code>. Optional: <code>Asset ID</code>, <code>Assigned User</code>, <code>Notes</code>,
plus <code>cf_&lt;field_key&gt;</code> for custom fields. No Folder column needed.
</p>
<div id="importResult" class="mt-3"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" id="importSubmitBtn">Import</button>
</div>
</form>
</div>
</div>
</div>
{{end}}
{{end}}