Compare commits
3 Commits
587dac99c5
...
0233453084
Author | SHA1 | Date | |
---|---|---|---|
0233453084 | |||
c026b5f1a8 | |||
5d99baea8e |
@ -10,7 +10,9 @@
|
|||||||
"/@popperjs/core/dist/umd/popper.min.js.map",
|
"/@popperjs/core/dist/umd/popper.min.js.map",
|
||||||
"/bootstrap/dist/js/bootstrap.bundle.min.js.map",
|
"/bootstrap/dist/js/bootstrap.bundle.min.js.map",
|
||||||
"/bootstrap-icons/font/fonts/bootstrap-icons.woff",
|
"/bootstrap-icons/font/fonts/bootstrap-icons.woff",
|
||||||
"/tsparticles-confetti/tsparticles.confetti.bundle.min.js"
|
"/tsparticles-confetti/tsparticles.confetti.bundle.min.js",
|
||||||
|
"/bootstrap-table/dist/bootstrap-table.min.js",
|
||||||
|
"/bootstrap-table/dist/bootstrap-table.min.css"
|
||||||
],
|
],
|
||||||
"debugMode": false
|
"debugMode": false
|
||||||
}
|
}
|
||||||
|
9
package-lock.json
generated
9
package-lock.json
generated
@ -16,6 +16,7 @@
|
|||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"bootstrap": "^5.3.0-alpha3",
|
"bootstrap": "^5.3.0-alpha3",
|
||||||
"bootstrap-icons": "^1.10.5",
|
"bootstrap-icons": "^1.10.5",
|
||||||
|
"bootstrap-table": "^1.22.1",
|
||||||
"csv": "^6.2.11",
|
"csv": "^6.2.11",
|
||||||
"eta": "^2.0.1",
|
"eta": "^2.0.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
@ -1299,6 +1300,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/bootstrap-table": {
|
||||||
|
"version": "1.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.22.1.tgz",
|
||||||
|
"integrity": "sha512-Nw8p+BmaiMDSfoer/p49YeI3vJQAWhudxhyKMuqnJBb3NRvCRewMk7JDgiN9SQO3YeSejOirKtcdWpM0dtddWg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"jquery": "3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"bootstrap": "^5.3.0-alpha3",
|
"bootstrap": "^5.3.0-alpha3",
|
||||||
"bootstrap-icons": "^1.10.5",
|
"bootstrap-icons": "^1.10.5",
|
||||||
|
"bootstrap-table": "^1.22.1",
|
||||||
"csv": "^6.2.11",
|
"csv": "^6.2.11",
|
||||||
"eta": "^2.0.1",
|
"eta": "^2.0.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
@ -88,6 +88,26 @@ export function parseIntRelation(data: string, relation_name: string = 'id', doN
|
|||||||
}`);
|
}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseIntOrUndefined(data: string) {
|
|
||||||
|
/**
|
||||||
|
* Function to parse a string into a number or return undefined if it is not a number
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string || any} data
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function parseIntOrUndefined(data: any) {
|
||||||
return isNaN(parseInt(data)) ? undefined : parseInt(data);
|
return isNaN(parseInt(data)) ? undefined : parseInt(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to create a sortBy compatible object from a string
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {string} SortField
|
||||||
|
* @param {string} Order
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function parseDynamicSortBy(SortField: string, Order: string){
|
||||||
|
return JSON.parse(`{ "${SortField}": "${Order}" }`);
|
||||||
|
}
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
<%~ E.includeFile("partials/head.eta.html", {"title": "Dashboard"}) %> <%~ E.includeFile("partials/controls.eta.html", {"active": "Dashboard"}) %>
|
<%~ E.includeFile("partials/head.eta.html", {"title": "Dashboard"}) %> <%~ E.includeFile("partials/controls.eta.html", {"active": "Dashboard"}) %>
|
||||||
|
|
||||||
<h1 onclick="doTheConfetti()" class="user-select-none">Good evening, ${user}</h1>
|
<h1 onclick="doTheConfetti()" class="user-select-none" id="greeting">Good evening, ${user}</h1>
|
||||||
|
<script>
|
||||||
|
// Handle greeting
|
||||||
|
var today = new Date();
|
||||||
|
var curHr = today.getHours();
|
||||||
|
|
||||||
|
if (curHr < 12) {
|
||||||
|
document.getElementById("greeting").innerHTML = "Good morning";
|
||||||
|
} else if (curHr < 18) {
|
||||||
|
document.getElementById("greeting").innerHTML = "Good afternoon";
|
||||||
|
} else {
|
||||||
|
document.getElementById("greeting").innerHTML = "Good evening";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="card col m-2">
|
<div class="card col m-2">
|
||||||
|
@ -93,13 +93,13 @@
|
|||||||
<a href="/settings/category/new" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#itemModifyModal" onclick="primeCreateNew()">Create new item</a>
|
<a href="/settings/category/new" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#itemModifyModal" onclick="primeCreateNew()">Create new item</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table align-middle">
|
<table class="table align-middle" id="itemList" data-sortable="true" data-search-highlight="true" data-pagination="true" data-page-size="25" data-remember-order="true">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">SKU</th>
|
<th scope="col" data-field="SKU" class="sku" data-sortable="true">SKU</th>
|
||||||
<th scope="col">Name</th>
|
<th scope="col" data-field="name" data-sortable="true">Name</th>
|
||||||
<th scope="col">Status</th>
|
<th scope="col" data-field="status" data-sortable="true">Status</th>
|
||||||
<th scope="col">Actions</th>
|
<th scope="col" data-field="actions" data-searchable="false">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<% if(it.items.length == 0) { %>
|
<% if(it.items.length == 0) { %>
|
||||||
@ -109,49 +109,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<% } %>
|
<% } %>
|
||||||
<tbody>
|
|
||||||
<% it.items.forEach(function(user){ %>
|
|
||||||
<tr>
|
|
||||||
<td scope="row" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="ID: <%= user.id %>">
|
|
||||||
<% if (user.SKU == null) { %>
|
|
||||||
<i>No SKU assigned</i>
|
|
||||||
<% } else { %> <%= user.SKU %> <% } %></td>
|
|
||||||
<td><%= user.name %></td>
|
|
||||||
<% if(user.status == "normal") { %>
|
|
||||||
|
|
||||||
<td><span class="badge text-bg-success"><%= user.status %></span></td>
|
|
||||||
<% } else if(user.status == "stolen") { %>
|
|
||||||
<td><span class="badge text-bg-danger"><%= user.status %></span></td>
|
|
||||||
<% } else if(user.status == "lost") { %>
|
|
||||||
<td><span class="badge text-bg-warning"><%= user.status %></span></td>
|
|
||||||
<% } else if(user.status == "borrowed") { %>
|
|
||||||
<td><span class="badge text-bg-info"><%= user.status %></span></td>
|
|
||||||
<% } %>
|
|
||||||
<td>
|
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#itemModifyModal" onclick="primeEdit(); getDataForEdit('<%= user.id %>')">
|
|
||||||
<i class="bi bi-pencil"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-danger" onclick="preFillDeleteModalNxt('<%= user.id %>','items','Item')" data-bs-toggle="modal" data-bs-target="#staticBackdrop">
|
|
||||||
<i class="bi bi-trash"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% }) %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
<br />
|
|
||||||
<% if(it.maxPages > 1) { %>
|
|
||||||
<nav aria-label="Page selector">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<li class="page-item <%= it.currentPage-1 < 1 ? 'disabled' : ''%>"><a class="page-link" href="?page=<%= it.currentPage - 1 %>">Previous</a></li>
|
|
||||||
<% for (var i = 1; i <= it.maxPages; i++) { %>
|
|
||||||
<li class="page-item <%= it.currentPage == i ? 'active' : ''%>"><a class="page-link" href="?page=<%= i %>"><%= i %></a></li>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<li class="page-item <%= it.currentPage+1 > it.maxPages ? 'disabled' : ''%>"><a class="page-link" href="?page=<%= it.currentPage + 1 %>">Next</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
</div>
|
||||||
<script src="/js/editItems.js"></script>
|
<script src="/js/editItems.js"></script>
|
||||||
|
<script src="/js/itemPageHandler.js"></script>
|
||||||
<%~ E.includeFile("partials/controlsFoot.eta.html") %> <%~ E.includeFile("partials/foot.eta.html") %>
|
<%~ E.includeFile("partials/controlsFoot.eta.html") %> <%~ E.includeFile("partials/foot.eta.html") %>
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Align the mode picker at the bottom of the navbar -->
|
<!-- Align the mode picker at the bottom of the navbar -->
|
||||||
|
|
||||||
<ul class="nav flex-column mb-2 position-absolute bottom-0 align-items-center w-100">
|
<ul class="nav flex-column mb-5 position-absolute bottom-0 align-items-center w-100">
|
||||||
<div class="input-group mb-3 justify-content-center w-100">
|
<div class="input-group mb-3 justify-content-center w-100">
|
||||||
<label class="btn btn-secondary" for="mode_light"><i class="bi bi-brightness-high"></i></label>
|
<label class="btn btn-secondary" for="mode_light"><i class="bi bi-brightness-high"></i></label>
|
||||||
<input type="radio" class="btn-check" name="options" id="mode_light" autocomplete="off" />
|
<input type="radio" class="btn-check" name="options" id="mode_light" autocomplete="off" />
|
||||||
@ -177,7 +177,23 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</ul>
|
</ul>
|
||||||
<div onclick="toggleAutoReload();" class="text-secondary versionInfo nav flex-column position-absolute bottom-0 align-items-center w-100">AssetFlow Alpha V0.0.1 </div>
|
<div onclick="toggleAutoReload();" class="text-secondary versionInfo nav flex-column position-absolute bottom-0 align-items-center w-100" id="versionInfo">AssetFlow Alpha <i>No version info</i> </div>
|
||||||
|
<script>
|
||||||
|
// Request /api/v1/version
|
||||||
|
// If the response is 200, set the commit hash
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: "/api/v1/version",
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (data) {
|
||||||
|
$('#versionInfo').text(`AssetFlow Alpha ${data.version} ${data.commit}`);
|
||||||
|
},
|
||||||
|
error: function (data) {
|
||||||
|
createNewToast('<i class="bi bi-exclamation-triangle-fill"></i> Unable to load version information', "text-bg-danger", 3000, false)
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4" style="min-height: 100%">
|
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4" style="min-height: 100%">
|
||||||
|
@ -6,6 +6,17 @@
|
|||||||
<script src="/js/handleSidebarTriangles.js"></script>
|
<script src="/js/handleSidebarTriangles.js"></script>
|
||||||
<script src="/js/formHandler.js"></script>
|
<script src="/js/formHandler.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
|
function activateTooltips(){
|
||||||
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
|
// Enable all bootstrap tooltips.
|
||||||
</script>
|
// https://getbootstrap.com/docs/5.3/components/tooltips/#enable-tooltips
|
||||||
|
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||||
|
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||||
|
}
|
||||||
|
function activatePopovers(){
|
||||||
|
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
|
||||||
|
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
|
||||||
|
}
|
||||||
|
|
||||||
|
activatePopovers();
|
||||||
|
activateTooltips();
|
||||||
|
</script>
|
||||||
|
@ -1,8 +1,2 @@
|
|||||||
<script>
|
|
||||||
// Enable all bootstrap tooltips.
|
|
||||||
// https://getbootstrap.com/docs/5.3/components/tooltips/#enable-tooltips
|
|
||||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
|
||||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
<title>AssetFlow - <%= it.title %></title>
|
<title>AssetFlow - <%= it.title %></title>
|
||||||
<meta name="author" content="[Project-Name-Here]" />
|
<meta name="author" content="[Project-Name-Here]" />
|
||||||
|
|
||||||
<!--<link rel="icon" href="/favicon.ico" />-->
|
|
||||||
<link rel="icon" href="/logo/Design_icon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/logo/Design_icon.svg" type="image/svg+xml" />
|
||||||
|
|
||||||
<script src="/js/handleColorMode.js"></script>
|
<script src="/js/handleColorMode.js"></script>
|
||||||
@ -21,7 +20,8 @@
|
|||||||
<script src="/static/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="/static/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="/static/@popperjs/core/dist/umd/popper.min.js"></script>
|
<script src="/static/@popperjs/core/dist/umd/popper.min.js"></script>
|
||||||
<script src="/static/tsparticles-confetti/tsparticles.confetti.bundle.min.js"></script>
|
<script src="/static/tsparticles-confetti/tsparticles.confetti.bundle.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/static/bootstrap-table/dist/bootstrap-table.min.css">
|
||||||
|
<script src="/static/bootstrap-table/dist/bootstrap-table.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- The body and html tag need to be left open! -->
|
<!-- The body and html tag need to be left open! -->
|
||||||
|
@ -6,6 +6,7 @@ import itemRoute from './items.js';
|
|||||||
import categoryRoute from './categories.js';
|
import categoryRoute from './categories.js';
|
||||||
import storageUnitRoute from './storageUnits.js';
|
import storageUnitRoute from './storageUnits.js';
|
||||||
import storageLocationRoute from './storageLocations.js';
|
import storageLocationRoute from './storageLocations.js';
|
||||||
|
import versionRoute from './version.js'
|
||||||
|
|
||||||
import search_routes from './search/index.js';
|
import search_routes from './search/index.js';
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ Router.route('/categories').get(categoryRoute.get).post(categoryRoute.post).patc
|
|||||||
// TODO: Migrate routes to lowercase.
|
// TODO: Migrate routes to lowercase.
|
||||||
Router.route('/storageUnits').get(storageUnitRoute.get).post(storageUnitRoute.post).patch(storageUnitRoute.patch).delete(storageUnitRoute.del);
|
Router.route('/storageUnits').get(storageUnitRoute.get).post(storageUnitRoute.post).patch(storageUnitRoute.patch).delete(storageUnitRoute.del);
|
||||||
Router.route('/storageLocations').get(storageLocationRoute.get).post(storageLocationRoute.post).patch(storageLocationRoute.patch).delete(storageLocationRoute.del);
|
Router.route('/storageLocations').get(storageLocationRoute.get).post(storageLocationRoute.post).patch(storageLocationRoute.patch).delete(storageLocationRoute.del);
|
||||||
|
Router.route('/version').get(versionRoute.get);
|
||||||
Router.use('/search', search_routes);
|
Router.use('/search', search_routes);
|
||||||
|
|
||||||
Router.route('/test').get(testRoute.get);
|
Router.route('/test').get(testRoute.get);
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { prisma, __path, log } from '../../../index.js';
|
import { prisma, __path, log } from '../../../index.js';
|
||||||
import { itemStatus } from '@prisma/client';
|
import { itemStatus } from '@prisma/client';
|
||||||
import { parseIntRelation, parseIntOrUndefined } from '../../../assets/helper.js';
|
import { parseIntRelation, parseIntOrUndefined, parseDynamicSortBy } from '../../../assets/helper.js';
|
||||||
// Get item.
|
// Get item.
|
||||||
function get(req: Request, res: Response) {
|
async function get(req: Request, res: Response) {
|
||||||
if (req.query.getAll === undefined) {
|
// Set sane defaults if undefined.
|
||||||
// Check if required fields are present
|
if (req.query.sort === undefined) {
|
||||||
if (!req.query.id) {
|
req.query.sort = 'id';
|
||||||
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'One or more required fields are missing' });
|
}
|
||||||
return;
|
if (req.query.order === undefined) {
|
||||||
}
|
req.query.order = 'asc';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.id) {
|
||||||
// Check if number is a valid integer
|
// Check if number is a valid integer
|
||||||
if (!Number.isInteger(parseInt(req.query.id.toString()))) {
|
if (!Number.isInteger(parseInt(req.query.id.toString()))) {
|
||||||
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'The id field must be an integer' });
|
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'The id field must be an integer' });
|
||||||
@ -48,8 +50,53 @@ function get(req: Request, res: Response) {
|
|||||||
res.status(500).json({ errorcode: 'DB_ERROR', error: err });
|
res.status(500).json({ errorcode: 'DB_ERROR', error: err });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Get all items
|
||||||
|
const itemCountNotFiltered = await prisma.item.count({});
|
||||||
|
|
||||||
|
// Get all items (filtered)
|
||||||
|
const itemCountFiltered = await prisma.item.count({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
SKU: {
|
||||||
|
// Probably use prisma's Full-text search if it's out of beta
|
||||||
|
// @ts-ignore
|
||||||
|
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
// @ts-ignore
|
||||||
|
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString())
|
||||||
|
});
|
||||||
|
// log.core.debug('Dynamic relation:', parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString()));
|
||||||
|
|
||||||
prisma.item
|
prisma.item
|
||||||
.findMany({
|
.findMany({
|
||||||
|
take: parseIntOrUndefined(req.query.limit),
|
||||||
|
skip: parseIntOrUndefined(req.query.offset),
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
SKU: {
|
||||||
|
// @ts-ignore
|
||||||
|
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
// @ts-ignore
|
||||||
|
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString()),
|
||||||
// Get contactInfo, category, storageLocation( storageUnit<contactInfo> ) from relations.
|
// Get contactInfo, category, storageLocation( storageUnit<contactInfo> ) from relations.
|
||||||
include: {
|
include: {
|
||||||
contactInfo: true,
|
contactInfo: true,
|
||||||
@ -67,7 +114,7 @@ function get(req: Request, res: Response) {
|
|||||||
})
|
})
|
||||||
.then((items) => {
|
.then((items) => {
|
||||||
if (items) {
|
if (items) {
|
||||||
res.status(200).json(JSON.stringify(items));
|
res.status(200).json(JSON.stringify({ total: itemCountFiltered, totalNotFiltered: itemCountNotFiltered, items: items }));
|
||||||
} else {
|
} else {
|
||||||
res.status(410).json({ errorcode: 'NOT_EXISTING', error: 'Item does not exist' });
|
res.status(410).json({ errorcode: 'NOT_EXISTING', error: 'Item does not exist' });
|
||||||
}
|
}
|
||||||
@ -97,7 +144,7 @@ function post(req: Request, res: Response) {
|
|||||||
.create({
|
.create({
|
||||||
data: {
|
data: {
|
||||||
SKU: req.body.sku,
|
SKU: req.body.sku,
|
||||||
amount: parseIntOrUndefined(req.body.ammount), // FIXME: This is silently failing if NaN..
|
amount: parseIntOrUndefined(req.body.amount), // FIXME: This is silently failing if NaN..
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
comment: req.body.comment,
|
comment: req.body.comment,
|
||||||
status: req.body.status, // Only enum(itemStatus) values are valid
|
status: req.body.status, // Only enum(itemStatus) values are valid
|
||||||
@ -167,7 +214,7 @@ async function patch(req: Request, res: Response) {
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
SKU: req.body.sku,
|
SKU: req.body.sku,
|
||||||
amount: parseIntOrUndefined(req.body.ammount), // FIXME: This is silently failing if NaN..
|
amount: parseIntOrUndefined(req.body.amount), // FIXME: This is silently failing if NaN..
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
comment: req.body.comment,
|
comment: req.body.comment,
|
||||||
status: req.body.status, // Only enum(itemStatus) values are valid
|
status: req.body.status, // Only enum(itemStatus) values are valid
|
||||||
|
10
src/routes/api/v1/version.ts
Normal file
10
src/routes/api/v1/version.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
|
||||||
|
function get(req: Request, res: Response) {
|
||||||
|
const revision = require('child_process')
|
||||||
|
.execSync('git rev-parse --short HEAD')
|
||||||
|
.toString().trim()
|
||||||
|
res.status(200).send({ version: '1.0.0', commit: revision });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { get };
|
@ -2,33 +2,13 @@ import { Request, Response } from 'express';
|
|||||||
import { prisma, __path, log } from '../../index.js';
|
import { prisma, __path, log } from '../../index.js';
|
||||||
|
|
||||||
async function get(req: Request, res: Response) {
|
async function get(req: Request, res: Response) {
|
||||||
// If no page is provided redirect to first
|
|
||||||
if (req.query.page === undefined) {
|
|
||||||
res.redirect('?page=1');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let page = parseInt(req.query.page.toString());
|
|
||||||
const itemCount = await prisma.item.count({}); // Count all items in the DB
|
|
||||||
|
|
||||||
const takeSize = 25; // Amount of times per page
|
|
||||||
const pageSize = Math.ceil(itemCount / takeSize); // Amount of pages, always round up
|
|
||||||
|
|
||||||
// If page is less then 1 or more then the max page size redirect to first or last page. If itemCount is 0 do not redirect.
|
|
||||||
if (page < 1) {
|
|
||||||
res.redirect('?page=1');
|
|
||||||
return;
|
|
||||||
} else if (page > pageSize && itemCount !== 0) {
|
|
||||||
res.redirect('?page=' + pageSize);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prisma.item
|
prisma.item
|
||||||
.findMany({ skip: (page - 1) * takeSize, take: takeSize, orderBy: { SKU: "asc" } }) // Skip the amount of items per page times the page number minus 1; skip has to be (page-1)*takeSize because skip is 0 indexed
|
.findMany({}) // Skip the amount of items per page times the page number minus 1; skip has to be (page-1)*takeSize because skip is 0 indexed
|
||||||
.then((items) => {
|
.then((items) => {
|
||||||
prisma.storageLocation.findMany({}).then((locations) => {
|
prisma.storageLocation.findMany({}).then((locations) => {
|
||||||
prisma.itemCategory.findMany({}).then((categories) => {
|
prisma.itemCategory.findMany({}).then((categories) => {
|
||||||
prisma.contactInfo.findMany({}).then((contactInfo) => {
|
prisma.contactInfo.findMany({}).then((contactInfo) => {
|
||||||
res.render(__path + '/src/frontend/items.eta.html', { items: items, currentPage: page, maxPages: pageSize, storeLocs: locations, categories: categories, contactInfo: contactInfo });
|
res.render(__path + '/src/frontend/items.eta.html', { items: items, storeLocs: locations, categories: categories, contactInfo: contactInfo });
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
var amountOfForms = $('.frontendForm').length;
|
var amountOfForms = $('.frontendForm').length;
|
||||||
|
|
||||||
|
function isNewDataLoaderAvailable() {
|
||||||
|
try {
|
||||||
|
return FLAG_supports_new_data_loader;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$('.frontendForm').each(function () {
|
$('.frontendForm').each(function () {
|
||||||
// TODO Handle empty strings as null or undefined, not as ''
|
// TODO Handle empty strings as null or undefined, not as ''
|
||||||
$(this).on('submit', function (e) {
|
$(this).on('submit', function (e) {
|
||||||
@ -26,7 +35,12 @@ $('.frontendForm').each(function () {
|
|||||||
// Clear all fields
|
// Clear all fields
|
||||||
form.find('input, textarea').val('');
|
form.find('input, textarea').val('');
|
||||||
// Create toast
|
// Create toast
|
||||||
createNewToast('<i class="bi bi-check2"></i> Changes saved successfully.', "text-bg-success")
|
if(isNewDataLoaderAvailable()) {
|
||||||
|
createNewToast('<i class="bi bi-check2"></i> Changes saved successfully.', "text-bg-success", undefined, false)
|
||||||
|
} else {
|
||||||
|
createNewToast('<i class="bi bi-check2"></i> Changes saved successfully.', "text-bg-success")
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
error: function (data) {
|
error: function (data) {
|
||||||
console.log('error');
|
console.log('error');
|
||||||
|
@ -10,7 +10,6 @@ trinagles.each(function () {
|
|||||||
$(this).addClass('rotate');
|
$(this).addClass('rotate');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('target', target);
|
|
||||||
target.on('show.bs.collapse', function () {
|
target.on('show.bs.collapse', function () {
|
||||||
$(triTar).addClass('rotate');
|
$(triTar).addClass('rotate');
|
||||||
$(triTar).removeClass('derotate');
|
$(triTar).removeClass('derotate');
|
||||||
|
58
static/js/itemPageHandler.js
Normal file
58
static/js/itemPageHandler.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
const FLAG_supports_new_data_loader = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we ever implement items in items, have a look at this:
|
||||||
|
* https://examples.bootstrap-table.com/index.html?extensions/treegrid.html#extensions/treegrid.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
function loadPageData() {
|
||||||
|
const itemList = $('#itemList');
|
||||||
|
// itemList.empty();
|
||||||
|
itemList.bootstrapTable('destroy')
|
||||||
|
itemList.bootstrapTable({url: "/api/v1/items", search: true, showRefresh: true, responseHandler: dataResponseHandler, sidePagination: 'server', serverSort: true})
|
||||||
|
setTimeout(() => {
|
||||||
|
activateTooltips();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dataResponseHandler(res) {
|
||||||
|
json = JSON.parse(res);
|
||||||
|
// console.log(json)
|
||||||
|
totalNotFiltered = json.totalNotFiltered;
|
||||||
|
total = json.total;
|
||||||
|
json = json.items;
|
||||||
|
json.forEach((item) => {
|
||||||
|
colorStatus = '';
|
||||||
|
if(item.SKU == null) item.SKU = '<i>No SKU assigned</i>';
|
||||||
|
switch (item.status) {
|
||||||
|
case 'normal':
|
||||||
|
colorStatus = 'success';
|
||||||
|
break;
|
||||||
|
case 'stolen':
|
||||||
|
colorStatus = 'danger';
|
||||||
|
break;
|
||||||
|
case 'lost':
|
||||||
|
colorStatus = 'warning';
|
||||||
|
break;
|
||||||
|
case 'borrowed':
|
||||||
|
colorStatus = 'info';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
colorStatus = 'secondary';
|
||||||
|
}
|
||||||
|
item.status = `<span class="badge text-bg-${colorStatus}">${item.status}</span>`;
|
||||||
|
item.actions = `
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#itemModifyModal" onclick="primeEdit(); getDataForEdit('${item.id}')">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" onclick="preFillDeleteModalNxt('${item.id}','items','Item')" data-bs-toggle="modal" data-bs-target="#staticBackdrop">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>`
|
||||||
|
item.SKU = `<p data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="ID: ${item.id}">${item.SKU}</p>`
|
||||||
|
});
|
||||||
|
///// --------------------------------- /////
|
||||||
|
|
||||||
|
return {"rows": json, total: total, totalNotFiltered: totalNotFiltered, totalRows: total};
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPageData()
|
@ -26,6 +26,11 @@ function createNewToast(message, colorSelector, autoHideTime = 1500, autoReload
|
|||||||
targetContainer.appendChild(newToast);
|
targetContainer.appendChild(newToast);
|
||||||
currentToasts.push(newToast);
|
currentToasts.push(newToast);
|
||||||
$(newToast).toast('show');
|
$(newToast).toast('show');
|
||||||
|
try {
|
||||||
|
loadPageData();
|
||||||
|
} catch (error) {
|
||||||
|
console.debug("Page does not support new data loading.")
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
destroyToast(newToast.id);
|
destroyToast(newToast.id);
|
||||||
if (autoReload && !forceSkipReload) {
|
if (autoReload && !forceSkipReload) {
|
||||||
|
Loading…
Reference in New Issue
Block a user