Reworked loan display to accommodate loan notes

Loans are no longer displayed as a unified table, but instead each loan
gets a "card" that contains the relevant data for the loan.

This has had a fairly major html/css re-work as a side effect. Most views are
now structured with two div-based columns so as to avoid the large blank areas
that pure grid can lead to when elements are very different in height.
This commit is contained in:
Erik Thuning 2025-03-18 13:39:48 +01:00
parent afb3020a34
commit c8e3ff0212
7 changed files with 1060 additions and 1108 deletions

@ -153,6 +153,7 @@
</div>
¤¤ product_details ¤¤
<div class="column">
<div id="product-details">
<h2>Product details</h2>
<form id="product-data"
@ -306,8 +307,15 @@
<button>Upload</button>
</form>
</div>
<div id="product-label"
class="¤label_hidden¤">
<h2>Label</h2>
¤label¤
</div>
</div>
¤¤ product_details_readonly ¤¤
<div class="column">
<div id="product-details">
<h2>Product details</h2>
<div id="product-data"
@ -364,6 +372,7 @@
<h2>Attachments</h2>
¤attachments¤
</div>
</div>
¤¤ product_details_public ¤¤
<div id="product-details">
@ -411,15 +420,7 @@
</div>
¤¤ product_meta ¤¤
<div id="product-history">
<h2>Product history</h2>
¤history¤
</div>
<div id="product-label"
class="¤label_hidden¤">
<h2>Label</h2>
¤label¤
</div>
<div class="column">
<div id="product-direct-checkout" class="¤checkout_hidden¤">
<h2>Check out</h2>
<form class="light"
@ -454,11 +455,16 @@
autocomplete="off"
placeholder="Username"
required />
<button>
<button type="submit">
Check out
</button>
</form>
</div>
<div id="product-history">
<h2>Product history</h2>
¤history¤
</div>
</div>
¤¤ attachment_list ¤¤
<ul class="attachment-list">
@ -555,6 +561,7 @@
</p>
¤¤ user_details ¤¤
<div class="column">
<div id="user-details">
<form onSubmit="JavaScript:updateUser(event)">
<input type="hidden"
@ -619,6 +626,8 @@
</button>
</form>
</div>
</div>
<div class="column">
<div id="active-loans">
<h2>Current loans</h2>
¤active_loans¤
@ -627,110 +636,63 @@
<h2>Old loans</h2>
¤inactive_loans¤
</div>
</div>
¤¤ user_loan_table ¤¤
<table class="history">
<thead>
¤¤ user_loan_card ¤¤
<div class="loan">
<div class="status ¤status¤"></div>
<table class="data">
<tr>
<th class="status">
</th>
<th>
Product
</th>
<th>
Serial number
</th>
<th>
Start date
</th>
<th>
End date
</th>
<th>
Started by
</th>
<th>
Misc
</th>
<th>Product:</th>
<td>¤name¤</td>
</tr>
<tr>
<th>Serial:</th>
<td>¤serial¤</td>
</tr>
<tr>
<th>Loan created by:</th>
<td>¤initiator¤</td>
</tr>
<tr>
<th>Validity:</th>
<td>¤start_date¤ - ¤end_date¤</td>
</tr>
<tr class="¤hidden¤">
<th>Notes:</th>
<td class="notes">¤notes¤</td>
</tr>
</thead>
<tbody class="single">
¤rows¤
</tbody>
</table>
¤¤ user_loan_table_row ¤¤
<tr>
<td class="status ¤status¤">
</td>
<td>
¤name¤
</td>
<td>
¤serial¤
</td>
<td>
¤start_date¤
</td>
<td>
¤end_date¤
</td>
<td>
¤initiator¤
</td>
<td>
¤note¤
</td>
</tr>
¤¤ product_loan_table ¤¤
<table class="history">
<thead>
<tr>
<th class="status">
</th>
<th>
Borrower
</th>
<th>
Start date
</th>
<th>
End date
</th>
<th>
Started by
</th>
<th>
Misc
</th>
</tr>
</thead>
<tbody class="single">
¤rows¤
</tbody>
</table>
¤¤ product_loan_table_row ¤¤
<tr>
<td class="status ¤status¤">
</td>
<td>
¤name¤
</td>
<td>
¤start_date¤
</td>
<td>
¤end_date¤
</td>
<td>
¤initiator¤
</td>
<td>
<div class="misc">
¤misc¤
</td>
</div>
</div>
¤¤ product_loan_card ¤¤
<div class="loan">
<div class="status ¤status¤"></div>
<table class="data">
<tr>
<th>Loaned to:</th>
<td>¤name¤</td>
</tr>
<tr>
<th>Loan created by:</th>
<td>¤initiator¤</td>
</tr>
<tr>
<th>Validity:</th>
<td>¤start_date¤ - ¤end_date¤</td>
</tr>
<tr class="¤hidden¤">
<th>Notes:</th>
<td class="notes">¤notes¤</td>
</tr>
</table>
<div class="misc">
¤misc¤
</div>
</div>
¤¤ public_user_details ¤¤
<div id="public-message" class="¤hidden¤">
@ -809,6 +771,7 @@
</form>
¤¤ checkout_page ¤¤
<div class="column">
<div id="user-select">
<h2>Choose borrower</h2>
<form class="dark"
@ -902,12 +865,16 @@
Check out
</button>
</form>
</div>
</div>
<div class="column">
¤subtitle¤
¤loan_table¤
</div>
¤¤ loan_preset_button ¤¤
<button onClick="JavaScript:loanLength(event, ¤count¤, '¤type¤')">
<button onClick="JavaScript:loanLength(event, ¤count¤, '¤type¤')"
type="button">
¤description¤
</button>

@ -153,6 +153,7 @@
</div>
¤¤ product_details ¤¤
<div class="column">
<div id="product-details">
<h2>Artikeldata</h2>
<form id="product-data"
@ -306,8 +307,15 @@
<button>Ladda upp</button>
</form>
</div>
<div id="product-label"
class="¤label_hidden¤">
<h2>Etikett</h2>
¤label¤
</div>
</div>
¤¤ product_details_readonly ¤¤
<div class="column">
<div id="product-details">
<h2>Artikeldata</h2>
<div id="product-data"
@ -364,6 +372,7 @@
<h2>Bilagor</h2>
¤attachments¤
</div>
</div>
¤¤ product_details_public ¤¤
<div id="product-details">
@ -411,15 +420,7 @@
</div>
¤¤ product_meta ¤¤
<div id="product-history">
<h2>Artikelhistorik</h2>
¤history¤
</div>
<div id="product-label"
class="¤label_hidden¤">
<h2>Etikett</h2>
¤label¤
</div>
<div class="column">
<div id="product-direct-checkout" class="¤checkout_hidden¤">
<h2>Låna ut</h2>
<form class="light"
@ -454,11 +455,16 @@
autocomplete="off"
placeholder="Användarnamn"
required />
<button>
<button type="submit">
Låna ut
</button>
</form>
</div>
<div id="product-history">
<h2>Artikelhistorik</h2>
¤history¤
</div>
</div>
¤¤ attachment_list ¤¤
<ul class="attachment-list">
@ -555,6 +561,7 @@
</p>
¤¤ user_details ¤¤
<div class="column">
<div id="user-details">
<form onSubmit="JavaScript:updateUser(event)">
<input type="hidden"
@ -619,6 +626,8 @@
</button>
</form>
</div>
</div>
<div class="column">
<div id="active-loans">
<h2>Aktuella lån</h2>
¤active_loans¤
@ -627,110 +636,63 @@
<h2>Gamla lån</h2>
¤inactive_loans¤
</div>
</div>
¤¤ user_loan_table ¤¤
<table class="history">
<thead>
¤¤ user_loan_card ¤¤
<div class="loan">
<div class="status ¤status¤"></div>
<table class="data">
<tr>
<th class="status">
</th>
<th>
Artikel
</th>
<th>
Serienummer
</th>
<th>
Startdatum
</th>
<th>
Slutdatum
</th>
<th>
Startat av
</th>
<th>
Övrigt
</th>
<th>Artikel:</th>
<td>¤name¤</td>
</tr>
<tr>
<th>Serienummer:</th>
<td>¤serial¤</td>
</tr>
<tr>
<th>Lån skapat av:</th>
<td>¤initiator¤</td>
</tr>
<tr>
<th>Löptid:</th>
<td>¤start_date¤ - ¤end_date¤</td>
</tr>
<tr class="¤hidden¤">
<th>Anteckningar:</th>
<td class="notes">¤notes¤</td>
</tr>
</thead>
<tbody class="single">
¤rows¤
</tbody>
</table>
¤¤ user_loan_table_row ¤¤
<tr>
<td class="status ¤status¤">
</td>
<td>
¤name¤
</td>
<td>
¤serial¤
</td>
<td>
¤start_date¤
</td>
<td>
¤end_date¤
</td>
<td>
¤initiator¤
</td>
<td>
¤note¤
</td>
</tr>
¤¤ product_loan_table ¤¤
<table class="history">
<thead>
<tr>
<th class="status">
</th>
<th>
Låntagare
</th>
<th>
Startdatum
</th>
<th>
Slutdatum
</th>
<th>
Startat av
</th>
<th>
Övrigt
</th>
</tr>
</thead>
<tbody class="single">
¤rows¤
</tbody>
</table>
¤¤ product_loan_table_row ¤¤
<tr>
<td class="status ¤status¤">
</td>
<td>
¤name¤
</td>
<td>
¤start_date¤
</td>
<td>
¤end_date¤
</td>
<td>
¤initiator¤
</td>
<td>
<div class="misc">
¤misc¤
</td>
</div>
</div>
¤¤ product_loan_card ¤¤
<div class="loan">
<div class="status ¤status¤"></div>
<table class="data">
<tr>
<th>Utlånad till:</th>
<td>¤name¤</td>
</tr>
<tr>
<th>Lån skapat av:</th>
<td>¤initiator¤</td>
</tr>
<tr>
<th>Löptid:</th>
<td>¤start_date¤ - ¤end_date¤</td>
</tr>
<tr class="¤hidden¤">
<th>Anteckningar:</th>
<td class="notes">¤notes¤</td>
</tr>
</table>
<div class="misc">
¤misc¤
</div>
</div>
¤¤ public_user_details ¤¤
<div id="public-message" class="¤hidden¤">
@ -808,8 +770,8 @@
</button>
</form>
¤¤ checkout_page ¤¤
<div class="column">
<div id="user-select">
<h2>Välj låntagare</h2>
<form class="dark"
@ -903,12 +865,16 @@
Låna ut
</button>
</form>
</div>
</div>
<div class="column">
¤subtitle¤
¤loan_table¤
</div>
¤¤ loan_preset_button ¤¤
<button onClick="JavaScript:loanLength(event, ¤count¤, '¤type¤')">
<button onClick="JavaScript:loanLength(event, ¤count¤, '¤type¤')"
type="button">
¤description¤
</button>

@ -38,9 +38,7 @@ class NewPage extends Page {
$this->fragments['tag']);
}
}
$out = replace(array('template' => $template),
$this->fragments['template_management']);
$out .= replace(array('id' => '',
$out = replace(array('id' => '',
'name' => '',
'brand' => '',
'serial' => '',
@ -48,8 +46,11 @@ class NewPage extends Page {
'tags' => $tags,
'info' => $fields,
'label' => '',
'label_hidden' => 'hidden',
'hidden' => 'hidden'),
$this->fragments['product_details']);
$out .= replace(array('template' => $template),
$this->fragments['template_management']);
return $out;
}
}

@ -233,10 +233,15 @@ abstract class Page extends Responder {
'page' => 'users'),
$this->fragments['item_link']);
}
$note = '';
$notes = $loan->get_notes();
$hidden = 'hidden';
if($notes) {
$hidden = '';
}
$misc = '';
if($status !== 'inactive_loan') {
$extend = format_date($loan->get_endtime());
$note = replace(array('id' => $product->get_id(),
$misc = replace(array('id' => $product->get_id(),
'end_new' => $extend),
$this->fragments['loan_extend_form']);
}
@ -251,11 +256,12 @@ abstract class Page extends Responder {
'start_date' => format_date($start),
'end_date' => format_date($end),
'initiator' => $initiator_name,
'note' => $note),
$this->fragments['user_loan_table_row']);
'hidden' => $hidden,
'notes' => $notes,
'misc' => $misc),
$this->fragments['user_loan_card']);
}
return replace(array('rows' => $rows),
$this->fragments['user_loan_table']);
return $rows;
}
final protected function build_seen_table($products, $inventory) {

@ -112,14 +112,18 @@ class ProductPage extends Page {
$rows = '';
foreach($history as $event) {
$status = $event->get_status();
$itemlink = 'Service';
$userlink = 'Service';
$start = $event->get_starttime();
$end = $event->get_returntime();
$notes = $event->get_notes();
$hidden = 'hidden';
if($notes) {
$hidden = '';
}
$initiator = $event->get_initiator();
$initiator_name = i18n('Unknown');
$initiator_link = i18n('Unknown');
if($initiator) {
$initiator_name = replace(
$initiator_link = replace(
array('id' => $initiator->get_id(),
'name' => $initiator->get_name(),
'page' => 'users'),
@ -129,7 +133,7 @@ class ProductPage extends Page {
if($event instanceof Loan) {
$user = $event->get_user();
$product = $event->get_product();
$itemlink = replace(array('id' => $user->get_id(),
$userlink = replace(array('id' => $user->get_id(),
'name' => $user->get_name(),
'page' => 'users'),
$this->fragments['item_link']);
@ -142,16 +146,16 @@ class ProductPage extends Page {
}
}
$rows .= replace(array('status' => $status,
'name' => $itemlink,
'name' => $userlink,
'start_date' => format_date($start),
'end_date' => format_date($end),
'initiator' => $initiator_name,
'initiator' => $initiator_link,
'hidden' => $hidden,
'notes' => $notes,
'misc' => $misc),
$this->fragments['product_loan_table_row']);
$this->fragments['product_loan_card']);
}
return replace(array('rows' => $rows),
$this->fragments['product_loan_table']);
return $rows;
}

103
style.css

@ -10,6 +10,18 @@ ul {
padding-left: 15px;
}
#contents {
display: grid;
grid-template-columns: [col] auto [col] auto [col];
grid-template-rows: auto [row] repeat(3, auto [row]);
grid-template-areas: "header header"
"first second"
"third fourth";
gap: 1rem;
justify-items: start;
align-items: start;
}
#message {
position: absolute;
top: 5px;
@ -54,7 +66,7 @@ ul {
}
input[type="text"].narrow {
width: 6em;
width: 6rem;
}
.hidden {
@ -65,6 +77,40 @@ form {
margin-bottom: 5px;
}
.column {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 1rem;
}
.loan {
display: grid;
grid-template-columns: 5px 1fr;
grid-template-rows: auto auto;
grid-auto-rows: auto;
background-color: #d7e0eb;
margin-bottom: 1rem;
margin-top: 1rem;
}
.loan .status {
grid-row: 1 / -1;
grid-column: 1 / 2;
}
.loan .data {
grid-row: 1;
grid-column: 2;
}
.loan .misc {
grid-row: 2;
grid-column: 2;
justify-self: end;
padding: 2px 5px;
}
table {
border-collapse: collapse;
}
@ -76,31 +122,35 @@ td, th {
padding: 2px 5px;
}
td.available {
background-color: #a3a86b;
td.notes {
white-space: pre-wrap;
}
td.status, th.status {
.status {
width: 5px;
}
td.on_loan, td.active_loan {
.status.available {
background-color: #a3a86b;
}
.status.on_loan, .status.active_loan {
background-color: #acdee6;
}
td.inactive_loan {
.status.inactive_loan {
background-color: #cdebf0;
}
td.overdue, td.overdue_loan {
.status.overdue, .status.overdue_loan {
background-color: #d95e00;
}
td.discarded {
.status.discarded {
background-color: #a0a0a0;
}
td.service, td.active_service, td.inactive_service {
.status.service, .status.active_service, .status.inactive_service {
background-color: #e7e08d;
}
@ -142,15 +192,15 @@ input:disabled, textarea:disabled {
input[type="text"].newtag,
input[type="text"].newfield {
width: 11.5em;
width: 10.5rem;
}
tbody.fixedwidth > tr > td {
min-width: 11.5em;
min-width: 10.5rem;
}
input[type="text"].newtemplate {
width: 8em;
width: 8rem;
}
.minibutton {
@ -188,43 +238,14 @@ h1 {
grid-area: header;
}
#product-details {
grid-column: col 1 / col 2;
grid-row: row 1 / row 3;
}
#user-select {
grid-area: first;
}
#user-table {
grid-area: first;
}
#user-details {
grid-area: first;
}
#active-loans {
grid-area: third;
}
#inactive-loans {
grid-area: fourth;
}
#public-message {
grid-area: first / first / second / second;
}
#product-checkout {
grid-area: third;
}
#product-create {
grid-area: first;
}
#product-table {
grid-area: first;
}

@ -351,16 +351,3 @@ button:disabled, input[type="submit"]:disabled {
width: calc(100% - 8px);
}
}
#contents {
display: grid;
grid-template-columns: [col] auto [col] auto [col];
grid-template-rows: auto [row] repeat(3, auto [row]);
grid-template-areas: "header header"
"first second"
"third fourth"
"fifth sixth";
grid-gap: 1rem;
justify-items: start;
align-items: start;
}