Compare commits

...

10 Commits
1.5.0 ... 1.5.1

11 changed files with 3114 additions and 3346 deletions

View File

@@ -2,9 +2,24 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.5.1](https://git.odit.services/lfk/frontend/compare/1.5.0...1.5.1)
- chore(deps): pnpm@9 [`35bec9f`](https://git.odit.services/lfk/frontend/commit/35bec9fe584b93cd52e8bab4e469713468a67f70)
- chore(deps): bump some [`8fae1fb`](https://git.odit.services/lfk/frontend/commit/8fae1fb6b3e033f789d2568cbd2640c0d163dc53)
- fix(scanstations): CopyScanStationTokenModal open after create [`372fa11`](https://git.odit.services/lfk/frontend/commit/372fa110ec402dae166a302f2209c79353983148)
- fix(config): add explicit window.config [`91d2f46`](https://git.odit.services/lfk/frontend/commit/91d2f46b934bcba1429bd1d96e772c25c42a3e28)
- fix(dockerfile): AS casing [`50e81a6`](https://git.odit.services/lfk/frontend/commit/50e81a6cb5773381e153cbec3bed7db820ced84a)
- refactor(scanstations/CopyScanStationTokenModal): drop dispatch [`a5e72a1`](https://git.odit.services/lfk/frontend/commit/a5e72a18e368b5a7ee7b4e1894de613ecb767f28)
- chore(deps): node:23.2.0 [`93d67bd`](https://git.odit.services/lfk/frontend/commit/93d67bdba90a67b45d8895d9facaf66e908d53d6)
- fix(tracks/AddTrackModal): i18n [`c60bae4`](https://git.odit.services/lfk/frontend/commit/c60bae45334c2aa90d8931da07691c196469da46)
- fix: tailwind config [`43ac878`](https://git.odit.services/lfk/frontend/commit/43ac878d44b556c6d7811610f6fe0c9a5eff305f)
#### [1.5.0](https://git.odit.services/lfk/frontend/compare/1.4.13...1.5.0)
> 20 November 2024
- feat(ci)!: Switch to woodpecker [`fb8206f`](https://git.odit.services/lfk/frontend/commit/fb8206ff130f4f65dcf619a2a786e7d5895b77a1)
- 🚀RELEASE v1.5.0 [`ceabd06`](https://git.odit.services/lfk/frontend/commit/ceabd06a4319c3c9ffab680f909730d5bd789540)
- fix(components): Add missing toast imports [`9bfc0c5`](https://git.odit.services/lfk/frontend/commit/9bfc0c5338933e832d5df50457c7978c026d8df6)
#### [1.4.13](https://git.odit.services/lfk/frontend/compare/1.4.12...1.4.13)

View File

@@ -1,9 +1,9 @@
FROM registry.odit.services/hub/library/node:20.0.0-alpine3.17 as build
FROM registry.odit.services/hub/library/node:23.2.0-alpine3.20 AS build
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app
COPY package.json pnpm-lock.yaml vite.config.js tailwind.config.js postcss.config.cjs index.html ./
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8
COPY package.json pnpm-lock.yaml vite.config.js tailwind.config.cjs postcss.config.cjs index.html ./
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@9
RUN mkdir /pnpm && pnpm config set store-dir /pnpm && pnpm i
COPY src ./src
@@ -11,6 +11,6 @@ COPY public ./public
RUN pnpm build
# final image
FROM registry.odit.services/library/nginx-brotli:3.15 as final
FROM registry.odit.services/library/nginx-brotli:3.15 AS final
COPY --from=build /app/dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf

View File

@@ -13,7 +13,7 @@
<body>
<span style="display: none; visibility: hidden" id="buildinfo"
>RELEASE_INFO-1.5.0-RELEASE_INFO</span
>RELEASE_INFO-1.5.1-RELEASE_INFO</span
>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>

View File

@@ -1,6 +1,6 @@
{
"name": "@odit/lfk-frontend",
"version": "1.5.0",
"version": "1.5.1",
"type": "module",
"scripts": {
"i18n-order": "node order.js",
@@ -14,14 +14,14 @@
"devDependencies": {
"@odit/license-exporter": "0.2.0",
"@sveltejs/vite-plugin-svelte": "2.1.1",
"auto-changelog": "2.4.0",
"autoprefixer": "10.4.14",
"postcss": "8.4.23",
"prettier": "2.8.8",
"prettier-plugin-svelte": "2.10.0",
"release-it": "15.10.3",
"auto-changelog": "2.5.0",
"autoprefixer": "10.4.20",
"postcss": "8.4.49",
"prettier": "3.3.3",
"prettier-plugin-svelte": "3.2.8",
"release-it": "17.10.0",
"svelte-select": "3.17.0",
"tailwindcss": "3.3.2",
"tailwindcss": "3.4.15",
"vite": "4.3.3"
},
"release-it": {
@@ -42,11 +42,11 @@
}
},
"dependencies": {
"@odit/lfk-client-js": "1.1.2",
"@paralleldrive/cuid2": "2.2.0",
"@odit/lfk-client-js": "1.1.3",
"@paralleldrive/cuid2": "2.2.2",
"@tanstack/svelte-table": "8.9.1",
"bwip-js": "3.4.0",
"check-password-strength": "2.0.7",
"check-password-strength": "2.0.10",
"csvtojson": "2.0.10",
"localforage": "1.10.0",
"marked": "4.3.0",

5559
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
const config = {
baseurl: "http://localhost:4010",
baseurl_documentserver: "http://localhost:4010/documents",
documentserver_key:
"NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe",
// optional
default_username: "demo",
default_password: "demo",
prefersHashRouting: true,
baseurl: "http://localhost:4010",
baseurl_documentserver: "http://localhost:4010/documents",
documentserver_key:
"NqZSYTy5AFQ7MppbLW5moqpTk7u7YrNUHKYhKYuThnnya2WpCOIU694hIZT1FzYe",
// optional
default_username: "demo",
default_password: "demo",
prefersHashRouting: true,
};
window.config = config;

View File

@@ -1,200 +1,199 @@
<script>
import { _ } from "svelte-i18n";
import { _ } from "svelte-i18n";
import { tick } from "svelte";
import bwipjs from "bwip-js";
import toast from "svelte-french-toast";
import { tick, createEventDispatcher } from "svelte";
import bwipjs from "bwip-js";
export let copy_modal_open;
export let new_station;
let valueCopy = null;
let areaDom;
let copied = false;
$: is_qrcode = false;
$: barcode = textToBase64Barcode(new_station.key, is_qrcode);
export let copy_modal_open;
export let new_station;
const dispatch = createEventDispatcher();
let valueCopy = null;
let areaDom;
let copied = false;
$: is_qrcode = false;
$: barcode = textToBase64Barcode(new_station.key, is_qrcode);
function close() {
copy_modal_open = false;
}
async function copy() {
valueCopy = new_station.key;
await tick();
areaDom.focus();
areaDom.select();
try {
const successful = document.execCommand("copy");
if (!successful) {
throw new Error();
}
toast($_("copied-token-to-clipboard"));
copied = true;
} catch (err) {
toast.Error($_("error-whyile-copying-to-clipboard"));
}
// we can notify by event or storage about copy status
valueCopy = null;
}
function close() {
copy_modal_open = false;
}
async function copy() {
valueCopy = new_station.key;
await tick();
areaDom.focus();
areaDom.select();
try {
const successful = document.execCommand("copy");
if (!successful) {
throw new Error();
}
toast($_("copied-token-to-clipboard"));
copied = true;
} catch (err) {
toast.Error($_("error-whyile-copying-to-clipboard"));
}
// we can notify by event or storage about copy status
valueCopy = null;
}
function textToBase64Barcode(text, is_qrcode) {
const canvas = document.createElement("canvas");
let bcid = "code128";
if (is_qrcode) {
bcid = "qrcode";
}
let codeconfig = {
bcid,
text: `${text}`,
scale: 4,
includetext: true,
textxalign: "center",
backgroundcolor: "ffffff",
};
if (bcid == "code128") {
codeconfig.height = 10;
}
bwipjs.toCanvas(canvas, codeconfig);
return canvas.toDataURL("image/png");
}
function textToBase64Barcode(text, is_qrcode) {
const canvas = document.createElement("canvas");
let bcid = "code128";
if (is_qrcode) {
bcid = "qrcode";
}
let codeconfig = {
bcid,
text: `${text}`,
scale: 4,
includetext: true,
textxalign: "center",
backgroundcolor: "ffffff",
};
if (bcid == "code128") {
codeconfig.height = 10;
}
bwipjs.toCanvas(canvas, codeconfig);
return canvas.toDataURL("image/png");
}
</script>
{#if copy_modal_open}
{#if valueCopy != null}
<textarea bind:this={areaDom}>{valueCopy}</textarea>
{/if}
<div class="fixed z-10 inset-0 overflow-y-auto">
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("token")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again"
)}
<br />
{$_("please-copy-the-token-and-store-it-somewhere-save")}
</p>
</div>
<div class="mt-2 mb-6">
<label
for="token"
class="block text-sm font-medium text-gray-700"
>{$_("token")}</label
>
<button on:click={copy} class="inline-flex">
<p
name="token"
class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
>
{new_station.key}
</p>
<div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z"
/></svg
>
</div>
</button>
<p class="text-gray-500 text-xs">
{$_("click-to-copy-token-to-clipboard")}
</p>
</div>
</div>
</div>
<div class="mx-auto text-center items-center">
<h2 class="text-lg leading-6 font-medium text-gray-900">
{$_("config-codes")}
</h2>
<span class="flex items-center text-center">
<p class="text-md text-gray-900 mr-3">Format:</p>
<label for="codeswitch" class="text-md text-gray-900 mr-3"
>Code128</label
>
<input
id="codeswitch"
type="checkbox"
bind:checked={is_qrcode}
class="relative shrink-0 w-[3.25rem] h-7 bg-gray-100 checked:bg-none checked:bg-blue-600 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 ring-1 ring-transparent focus:border-blue-600 focus:ring-blue-600 ring-offset-white focus:outline-none appearance-none before:inline-block before:w-6 before:h-6 before:bg-white checked:before:bg-blue-200 before:translate-x-0 checked:before:translate-x-full before:shadow before:rounded-full before:transform before:ring-0 before:transition before:ease-in-out before:duration-200"
/>
<label for="codeswitch" class="text-md text-gray-900 ml-3"
>QR-Code</label
>
</span>
<h3 class="leading-6 font-medium text-gray-900">
{$_("api-endpoint")}
</h3>
<img
class:w-[50%]={is_qrcode}
class:w-full={!is_qrcode}
class="md:w-auto mb-2 mx-auto"
alt="Registrierungscode"
src={textToBase64Barcode(window.config.baseurl, is_qrcode)}
/>
<h3 class="leading-6 font-medium text-gray-900">{$_("token")}</h3>
<img
class:w-[50%]={is_qrcode}
class:w-full={!is_qrcode}
class="md:w-auto mb-2 mx-auto"
alt="Registrierungscode"
src={barcode}
/>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={close}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("yes-i-copied-the-token")}
</button>
</div>
</div>
</div>
</div>
{#if valueCopy != null}
<textarea bind:this={areaDom}>{valueCopy}</textarea>
{/if}
<div class="fixed z-10 inset-0 overflow-y-auto">
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
>
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop"
/>
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path fill="none" d="M0 0h24v24H0z" />
<path
d="M4 5v11h16V5H4zM2 4a1 1 0 011-1h18a1 1 0 011 1v14H2V4zM1 19h22v2H1v-2z"
/></svg
>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_("token")}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_(
"the-scanstations-api-token-will-only-get-displayed-once-you-wont-be-able-to-change-or-view-it-again"
)}
<br />
{$_("please-copy-the-token-and-store-it-somewhere-save")}
</p>
</div>
<div class="mt-2 mb-6">
<label
for="token"
class="block text-sm font-medium text-gray-700"
>{$_("token")}</label
>
<button on:click={copy} class="inline-flex">
<p
name="token"
class:bg-green-200={copied}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
>
{new_station.key}
</p>
<div
class="bg-gray-200 border-gray-300 border-t border-b border-r text-black rounded-r-md sm:text-sm p-2 mt-1 cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
><path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M7 4V2h10v2h3l1 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V5l1-1h3zm0 2H5v14h14V6h-2v2H7V6zm2-2v2h6V4H9z"
/></svg
>
</div>
</button>
<p class="text-gray-500 text-xs">
{$_("click-to-copy-token-to-clipboard")}
</p>
</div>
</div>
</div>
<div class="mx-auto text-center items-center">
<h2 class="text-lg leading-6 font-medium text-gray-900">
{$_("config-codes")}
</h2>
<span class="flex items-center text-center">
<p class="text-md text-gray-900 mr-3">Format:</p>
<label for="codeswitch" class="text-md text-gray-900 mr-3"
>Code128</label
>
<input
id="codeswitch"
type="checkbox"
bind:checked={is_qrcode}
class="relative shrink-0 w-[3.25rem] h-7 bg-gray-100 checked:bg-none checked:bg-blue-600 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 ring-1 ring-transparent focus:border-blue-600 focus:ring-blue-600 ring-offset-white focus:outline-none appearance-none before:inline-block before:w-6 before:h-6 before:bg-white checked:before:bg-blue-200 before:translate-x-0 checked:before:translate-x-full before:shadow before:rounded-full before:transform before:ring-0 before:transition before:ease-in-out before:duration-200"
/>
<label for="codeswitch" class="text-md text-gray-900 ml-3"
>QR-Code</label
>
</span>
<h3 class="leading-6 font-medium text-gray-900">
{$_("api-endpoint")}
</h3>
<img
class:w-[50%]={is_qrcode}
class:w-full={!is_qrcode}
class="md:w-auto mb-2 mx-auto"
alt="Registrierungscode"
src={textToBase64Barcode(window.config.baseurl, is_qrcode)}
/>
<h3 class="leading-6 font-medium text-gray-900">{$_("token")}</h3>
<img
class:w-[50%]={is_qrcode}
class:w-full={!is_qrcode}
class="md:w-auto mb-2 mx-auto"
alt="Registrierungscode"
src={barcode}
/>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={close}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("yes-i-copied-the-token")}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -1,44 +1,229 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddScanStationModal from "./AddScanStationModal.svelte";
import CopyScanStationTokenModal from "./CopyScanStationTokenModal.svelte";
import ScanStationsOverview from "./ScanStationsOverview.svelte";
export let modal_open = false;
export let copy_modal_open = false;
export let new_station = {};
let current_stations = [];
import { _ } from "svelte-i18n";
import store from "../../store";
import { ScanStationService } from "@odit/lfk-client-js";
import AddScanStationModal from "./AddScanStationModal.svelte";
import CopyScanStationTokenModal from "./CopyScanStationTokenModal.svelte";
import ScanStationsEmptyState from "./ScanStationsEmptyState.svelte";
import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte";
import toast from "svelte-french-toast";
//
export let modal_open = false;
export let copy_modal_open = false;
export let new_station = {};
//
const promise = ScanStationService.scanStationControllerGetAll().then(
(result) => {
current_stations = result;
}
);
$: searchvalue = "";
$: active_deletes = [];
let delete_station = {};
let current_stations = [];
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_("scanstations")}
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create-a-new-scanstation")}
</button>
{/if}
</span>
<ScanStationsOverview
bind:current_stations
bind:modal_open
bind:new_station
bind:copy_modal_open
/>
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_("scanstations")}
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:CREATE")}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
{$_("create-a-new-scanstation")}
</button>
{/if}
</span>
<ConfirmScanStationDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_station
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("scanstations-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_stations.length === 0}
<ScanStationsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")}
class="mb-4"
/>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr class="odd:bg-white even:bg-gray-100">
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("track")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("description")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("status")}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_("action")}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_stations as s}
{#if Object.values(s)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr
class="odd:bg-white even:bg-gray-100"
data-rowid="station_{s.id}"
>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>
{s.track.name || s.track.distance + "m"}</a
>
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{s.description}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if s.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("active")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("inactive")}</span
>
{/if}
</div>
</td>
{#if active_deletes[s.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button
on:click={() => {
active_deletes[s.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
ScanStationService.scanStationControllerRemove(
s.id,
false
)
.then((resp) => {
current_stations = current_stations.filter(
(obj) => obj.id !== s.id
);
toast($_("station-deleted"));
})
.catch((err) => {
modal_open = true;
delete_station = s;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a
href="/scanstations/{s.id}"
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:DELETE")}
<button
on:click={() => {
active_deletes[s.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500"
>
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_("general_promise_error")}</b>
{error}
</span>
</div>
{/await}
{/if}
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:CREATE")}
<AddScanStationModal
bind:modal_open
bind:current_stations
bind:new_station
bind:copy_modal_open
/>
<CopyScanStationTokenModal bind:copy_modal_open bind:new_station />
<AddScanStationModal
bind:modal_open
bind:current_stations
bind:new_station
bind:copy_modal_open
/>
<CopyScanStationTokenModal bind:copy_modal_open bind:new_station />
{/if}

View File

@@ -1,195 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import { ScanStationService } from "@odit/lfk-client-js";
const promise = ScanStationService.scanStationControllerGetAll().then(
(result) => {
current_stations = result;
}
);
import store from "../../store";
import ScanStationsEmptyState from "./ScanStationsEmptyState.svelte";
import ConfirmScanStationDeletion from "./ConfirmScanStationDeletion.svelte";
import toast from "svelte-french-toast";
$: searchvalue = "";
$: active_deletes = [];
let delete_station = {};
let modal_open = false;
export let current_stations = [];
</script>
<ConfirmScanStationDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_station
/>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:GET")}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert"
>
<p class="font-bold">{$_("scanstations-are-being-loaded")}</p>
<p class="text-sm">{$_("this-might-take-a-moment")}</p>
</div>
{:then}
{#if current_stations.length === 0}
<ScanStationsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_("datatable.search")}
aria-label={$_("datatable.search")}
class="mb-4"
/>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll"
>
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr class="odd:bg-white even:bg-gray-100">
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("track")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("description")}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{$_("status")}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_("action")}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_stations as s}
{#if Object.values(s)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr
class="odd:bg-white even:bg-gray-100"
data-rowid="station_{s.id}"
>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
<a
href="../tracks"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>
{s.track.name || s.track.distance + "m"}</a
>
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{s.description}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
{#if s.enabled}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>{$_("active")}</span
>
{:else}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800"
>{$_("inactive")}</span
>
{/if}
</div>
</td>
{#if active_deletes[s.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<button
on:click={() => {
active_deletes[s.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer"
>{$_("cancel-delete")}</button
>
<button
on:click={() => {
ScanStationService.scanStationControllerRemove(
s.id,
false
)
.then((resp) => {
current_stations = current_stations.filter(
(obj) => obj.id !== s.id
);
toast($_("station-deleted"));
})
.catch((err) => {
modal_open = true;
delete_station = s;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("confirm-delete")}</button
>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
>
<a
href="/scanstations/{s.id}"
class="text-indigo-600 hover:text-indigo-900"
>{$_("details")}</a
>
{#if store.state.jwtinfo.userdetails.permissions.includes("STATION:DELETE")}
<button
on:click={() => {
active_deletes[s.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer"
>{$_("delete")}</button
>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_("general_promise_error")}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@@ -223,7 +223,7 @@
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
>
Create
{$_("create")}
</button>
<button
on:click={() => {