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,7 +153,8 @@
</div> </div>
¤¤ product_details ¤¤ ¤¤ product_details ¤¤
<div id="product-details"> <div class="column">
<div id="product-details">
<h2>Product details</h2> <h2>Product details</h2>
<form id="product-data" <form id="product-data"
onSubmit="JavaScript:saveProduct(event)" onSubmit="JavaScript:saveProduct(event)"
@ -283,8 +284,8 @@
¤service¤ ¤service¤
</button> </button>
</form> </form>
</div> </div>
<div id="product-attachments" <div id="product-attachments"
class="¤hidden¤"> class="¤hidden¤">
<h2>Attachments</h2> <h2>Attachments</h2>
¤attachments¤ ¤attachments¤
@ -305,10 +306,17 @@
readonly /> readonly />
<button>Upload</button> <button>Upload</button>
</form> </form>
</div>
<div id="product-label"
class="¤label_hidden¤">
<h2>Label</h2>
¤label¤
</div>
</div> </div>
¤¤ product_details_readonly ¤¤ ¤¤ product_details_readonly ¤¤
<div id="product-details"> <div class="column">
<div id="product-details">
<h2>Product details</h2> <h2>Product details</h2>
<div id="product-data" <div id="product-data"
class="data"> class="data">
@ -358,11 +366,12 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<div id="product-attachments" <div id="product-attachments"
class="¤hidden¤"> class="¤hidden¤">
<h2>Attachments</h2> <h2>Attachments</h2>
¤attachments¤ ¤attachments¤
</div>
</div> </div>
¤¤ product_details_public ¤¤ ¤¤ product_details_public ¤¤
@ -411,16 +420,8 @@
</div> </div>
¤¤ product_meta ¤¤ ¤¤ product_meta ¤¤
<div id="product-history"> <div class="column">
<h2>Product history</h2> <div id="product-direct-checkout" class="¤checkout_hidden¤">
¤history¤
</div>
<div id="product-label"
class="¤label_hidden¤">
<h2>Label</h2>
¤label¤
</div>
<div id="product-direct-checkout" class="¤checkout_hidden¤">
<h2>Check out</h2> <h2>Check out</h2>
<form class="light" <form class="light"
onSubmit="JavaScript:checkoutProduct(event)"> onSubmit="JavaScript:checkoutProduct(event)">
@ -454,10 +455,15 @@
autocomplete="off" autocomplete="off"
placeholder="Username" placeholder="Username"
required /> required />
<button> <button type="submit">
Check out Check out
</button> </button>
</form> </form>
</div>
<div id="product-history">
<h2>Product history</h2>
¤history¤
</div>
</div> </div>
¤¤ attachment_list ¤¤ ¤¤ attachment_list ¤¤
@ -555,7 +561,8 @@
</p> </p>
¤¤ user_details ¤¤ ¤¤ user_details ¤¤
<div id="user-details"> <div class="column">
<div id="user-details">
<form onSubmit="JavaScript:updateUser(event)"> <form onSubmit="JavaScript:updateUser(event)">
<input type="hidden" <input type="hidden"
name="id" name="id"
@ -618,119 +625,74 @@
New loan New loan
</button> </button>
</form> </form>
</div>
</div> </div>
<div id="active-loans"> <div class="column">
<div id="active-loans">
<h2>Current loans</h2> <h2>Current loans</h2>
¤active_loans¤ ¤active_loans¤
</div> </div>
<div id="inactive-loans"> <div id="inactive-loans">
<h2>Old loans</h2> <h2>Old loans</h2>
¤inactive_loans¤ ¤inactive_loans¤
</div>
</div> </div>
¤¤ user_loan_table ¤¤ ¤¤ user_loan_card ¤¤
<table class="history"> <div class="loan">
<thead> <div class="status ¤status¤"></div>
<table class="data">
<tr> <tr>
<th class="status"> <th>Product:</th>
</th> <td>¤name¤</td>
<th>
Product
</th>
<th>
Serial number
</th>
<th>
Start date
</th>
<th>
End date
</th>
<th>
Started by
</th>
<th>
Misc
</th>
</tr> </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> <tr>
<th class="status"> <th>Serial:</th>
</th> <td>¤serial¤</td>
<th>
Borrower
</th>
<th>
Start date
</th>
<th>
End date
</th>
<th>
Started by
</th>
<th>
Misc
</th>
</tr> </tr>
</thead> <tr>
<tbody class="single"> <th>Loan created by:</th>
¤rows¤ <td>¤initiator¤</td>
</tbody> </tr>
</table> <tr>
<th>Validity:</th>
¤¤ product_loan_table_row ¤¤ <td>¤start_date¤ - ¤end_date¤</td>
<tr> </tr>
<td class="status ¤status¤"> <tr class="¤hidden¤">
</td> <th>Notes:</th>
<td> <td class="notes">¤notes¤</td>
¤name¤ </tr>
</td> </table>
<td> <div class="misc">
¤start_date¤
</td>
<td>
¤end_date¤
</td>
<td>
¤initiator¤
</td>
<td>
¤misc¤ ¤misc¤
</td> </div>
</tr> </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 ¤¤ ¤¤ public_user_details ¤¤
<div id="public-message" class="¤hidden¤"> <div id="public-message" class="¤hidden¤">
@ -809,7 +771,8 @@
</form> </form>
¤¤ checkout_page ¤¤ ¤¤ checkout_page ¤¤
<div id="user-select"> <div class="column">
<div id="user-select">
<h2>Choose borrower</h2> <h2>Choose borrower</h2>
<form class="dark" <form class="dark"
action="./" action="./"
@ -864,8 +827,8 @@
disabled>¤notes¤</textarea> disabled>¤notes¤</textarea>
</div> </div>
</form> </form>
</div> </div>
<div id="product-checkout"> <div id="product-checkout">
<h2>Check out product</h2> <h2>Check out product</h2>
<form class="light" <form class="light"
onSubmit="JavaScript:checkoutProduct(event)"> onSubmit="JavaScript:checkoutProduct(event)">
@ -902,12 +865,16 @@
Check out Check out
</button> </button>
</form> </form>
</div>
</div>
<div class="column">
¤subtitle¤ ¤subtitle¤
¤loan_table¤ ¤loan_table¤
</div> </div>
¤¤ loan_preset_button ¤¤ ¤¤ loan_preset_button ¤¤
<button onClick="JavaScript:loanLength(event, ¤count¤, '¤type¤')"> <button onClick="JavaScript:loanLength(event, ¤count¤, '¤type¤')"
type="button">
¤description¤ ¤description¤
</button> </button>

@ -153,7 +153,8 @@
</div> </div>
¤¤ product_details ¤¤ ¤¤ product_details ¤¤
<div id="product-details"> <div class="column">
<div id="product-details">
<h2>Artikeldata</h2> <h2>Artikeldata</h2>
<form id="product-data" <form id="product-data"
onSubmit="JavaScript:saveProduct(event)" onSubmit="JavaScript:saveProduct(event)"
@ -283,8 +284,8 @@
¤service¤ ¤service¤
</button> </button>
</form> </form>
</div> </div>
<div id="product-attachments" <div id="product-attachments"
class="¤hidden¤"> class="¤hidden¤">
<h2>Bilagor</h2> <h2>Bilagor</h2>
¤attachments¤ ¤attachments¤
@ -305,10 +306,17 @@
readonly /> readonly />
<button>Ladda upp</button> <button>Ladda upp</button>
</form> </form>
</div>
<div id="product-label"
class="¤label_hidden¤">
<h2>Etikett</h2>
¤label¤
</div>
</div> </div>
¤¤ product_details_readonly ¤¤ ¤¤ product_details_readonly ¤¤
<div id="product-details"> <div class="column">
<div id="product-details">
<h2>Artikeldata</h2> <h2>Artikeldata</h2>
<div id="product-data" <div id="product-data"
class="data"> class="data">
@ -358,11 +366,12 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<div id="product-attachments" <div id="product-attachments"
class="¤hidden¤"> class="¤hidden¤">
<h2>Bilagor</h2> <h2>Bilagor</h2>
¤attachments¤ ¤attachments¤
</div>
</div> </div>
¤¤ product_details_public ¤¤ ¤¤ product_details_public ¤¤
@ -411,16 +420,8 @@
</div> </div>
¤¤ product_meta ¤¤ ¤¤ product_meta ¤¤
<div id="product-history"> <div class="column">
<h2>Artikelhistorik</h2> <div id="product-direct-checkout" class="¤checkout_hidden¤">
¤history¤
</div>
<div id="product-label"
class="¤label_hidden¤">
<h2>Etikett</h2>
¤label¤
</div>
<div id="product-direct-checkout" class="¤checkout_hidden¤">
<h2>Låna ut</h2> <h2>Låna ut</h2>
<form class="light" <form class="light"
onSubmit="JavaScript:checkoutProduct(event)"> onSubmit="JavaScript:checkoutProduct(event)">
@ -454,10 +455,15 @@
autocomplete="off" autocomplete="off"
placeholder="Användarnamn" placeholder="Användarnamn"
required /> required />
<button> <button type="submit">
Låna ut Låna ut
</button> </button>
</form> </form>
</div>
<div id="product-history">
<h2>Artikelhistorik</h2>
¤history¤
</div>
</div> </div>
¤¤ attachment_list ¤¤ ¤¤ attachment_list ¤¤
@ -555,7 +561,8 @@
</p> </p>
¤¤ user_details ¤¤ ¤¤ user_details ¤¤
<div id="user-details"> <div class="column">
<div id="user-details">
<form onSubmit="JavaScript:updateUser(event)"> <form onSubmit="JavaScript:updateUser(event)">
<input type="hidden" <input type="hidden"
name="id" name="id"
@ -618,119 +625,74 @@
Nytt lån Nytt lån
</button> </button>
</form> </form>
</div>
</div> </div>
<div id="active-loans"> <div class="column">
<div id="active-loans">
<h2>Aktuella lån</h2> <h2>Aktuella lån</h2>
¤active_loans¤ ¤active_loans¤
</div> </div>
<div id="inactive-loans"> <div id="inactive-loans">
<h2>Gamla lån</h2> <h2>Gamla lån</h2>
¤inactive_loans¤ ¤inactive_loans¤
</div>
</div> </div>
¤¤ user_loan_table ¤¤ ¤¤ user_loan_card ¤¤
<table class="history"> <div class="loan">
<thead> <div class="status ¤status¤"></div>
<table class="data">
<tr> <tr>
<th class="status"> <th>Artikel:</th>
</th> <td>¤name¤</td>
<th>
Artikel
</th>
<th>
Serienummer
</th>
<th>
Startdatum
</th>
<th>
Slutdatum
</th>
<th>
Startat av
</th>
<th>
Övrigt
</th>
</tr> </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> <tr>
<th class="status"> <th>Serienummer:</th>
</th> <td>¤serial¤</td>
<th>
Låntagare
</th>
<th>
Startdatum
</th>
<th>
Slutdatum
</th>
<th>
Startat av
</th>
<th>
Övrigt
</th>
</tr> </tr>
</thead> <tr>
<tbody class="single"> <th>Lån skapat av:</th>
¤rows¤ <td>¤initiator¤</td>
</tbody> </tr>
</table> <tr>
<th>Löptid:</th>
¤¤ product_loan_table_row ¤¤ <td>¤start_date¤ - ¤end_date¤</td>
<tr> </tr>
<td class="status ¤status¤"> <tr class="¤hidden¤">
</td> <th>Anteckningar:</th>
<td> <td class="notes">¤notes¤</td>
¤name¤ </tr>
</td> </table>
<td> <div class="misc">
¤start_date¤
</td>
<td>
¤end_date¤
</td>
<td>
¤initiator¤
</td>
<td>
¤misc¤ ¤misc¤
</td> </div>
</tr> </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 ¤¤ ¤¤ public_user_details ¤¤
<div id="public-message" class="¤hidden¤"> <div id="public-message" class="¤hidden¤">
@ -808,9 +770,9 @@
</button> </button>
</form> </form>
¤¤ checkout_page ¤¤ ¤¤ checkout_page ¤¤
<div id="user-select"> <div class="column">
<div id="user-select">
<h2>Välj låntagare</h2> <h2>Välj låntagare</h2>
<form class="dark" <form class="dark"
action="./" action="./"
@ -865,8 +827,8 @@
disabled>¤notes¤</textarea> disabled>¤notes¤</textarea>
</div> </div>
</form> </form>
</div> </div>
<div id="product-checkout"> <div id="product-checkout">
<h2>Låna ut artikel</h2> <h2>Låna ut artikel</h2>
<form class="light" <form class="light"
onSubmit="JavaScript:checkoutProduct(event)"> onSubmit="JavaScript:checkoutProduct(event)">
@ -903,12 +865,16 @@
Låna ut Låna ut
</button> </button>
</form> </form>
</div>
</div>
<div class="column">
¤subtitle¤ ¤subtitle¤
¤loan_table¤ ¤loan_table¤
</div> </div>
¤¤ loan_preset_button ¤¤ ¤¤ loan_preset_button ¤¤
<button onClick="JavaScript:loanLength(event, ¤count¤, '¤type¤')"> <button onClick="JavaScript:loanLength(event, ¤count¤, '¤type¤')"
type="button">
¤description¤ ¤description¤
</button> </button>

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

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

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

103
style.css

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

@ -351,16 +351,3 @@ button:disabled, input[type="submit"]:disabled {
width: calc(100% - 8px); 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;
}