Merge branch 'WarehouseShippingNote/add-tablet-mode' into 'master'
added new tablet mode for creating shipping notes See merge request fronk/thetool!1602
This commit is contained in:
123
Layout/default/VueViews/WarehouseLoginOverride.php
Normal file
123
Layout/default/VueViews/WarehouseLoginOverride.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" class="h-full">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Lager-System Login</title>
|
||||
<meta name="theme-color" content="#0055FF"/>
|
||||
<link rel="manifest" href="/assets/pwa/shipping-note-tablet-manifest.json">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3.4.27/dist/vue.global.min.js"></script>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
window.WAREHOUSE_CONFIG = { userOptions: <?= json_encode($userOptions) ?> };
|
||||
tailwind.config = {
|
||||
theme: { extend: { colors: { 'brand-blue': '#0055FF' } } }
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
||||
.user-list-container::-webkit-scrollbar { width: 5px; }
|
||||
.user-list-container::-webkit-scrollbar-track { background: #f1f5f9; }
|
||||
.user-list-container::-webkit-scrollbar-thumb { background: #a8b2c1; border-radius: 10px; }
|
||||
.list-move, .list-enter-active, .list-leave-active { transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); }
|
||||
.list-enter-from, .list-leave-to { opacity: 0; transform: scale(0.95); }
|
||||
.list-leave-active { position: absolute !important; }
|
||||
.container-enter-active { transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
|
||||
.container-enter-from { opacity: 0; transform: translateY(20px); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-100 flex items-center justify-center min-h-full font-sans p-4">
|
||||
|
||||
<div id="app" class="w-full max-w-lg mx-auto"></div>
|
||||
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => navigator.serviceWorker.register('/assets/pwa/shipping-note-tablet-sw.js'));
|
||||
}
|
||||
|
||||
const {createApp, ref, computed, onMounted} = Vue;
|
||||
|
||||
createApp({
|
||||
setup() {
|
||||
const excludedNames = ['cli', 'spitzer', 'test'];
|
||||
const initialUsers = window.WAREHOUSE_CONFIG.userOptions.filter(user => {
|
||||
const nameLower = user.text.toLowerCase();
|
||||
return !excludedNames.some(ex => nameLower.includes(ex));
|
||||
});
|
||||
|
||||
const selectedLetter = ref('All');
|
||||
const selectedUserId = ref(null);
|
||||
const isMounted = ref(false);
|
||||
|
||||
onMounted(() => isMounted.value = true);
|
||||
|
||||
const availableLetters = computed(() => {
|
||||
const letters = new Set(initialUsers.map(u => u.text[0].toUpperCase()));
|
||||
return ['All', ...Array.from(letters).sort()];
|
||||
});
|
||||
|
||||
const filteredUsers = computed(() => {
|
||||
if (selectedLetter.value === 'All') return initialUsers;
|
||||
return initialUsers.filter(u => u.text.startsWith(selectedLetter.value));
|
||||
});
|
||||
|
||||
function selectUser(userId) {
|
||||
selectedUserId.value = userId;
|
||||
}
|
||||
|
||||
return { isMounted, availableLetters, selectedLetter, filteredUsers, selectedUserId, selectUser };
|
||||
},
|
||||
template: `
|
||||
<Transition name="container" appear>
|
||||
<div v-if="isMounted" class="bg-white p-6 sm:p-8 rounded-2xl shadow-2xl shadow-slate-300/60 w-full text-center">
|
||||
<header class="mb-6">
|
||||
<img src="/assets/images/xinon-full.png" alt="Logo" class="h-12 w-auto mx-auto mb-4">
|
||||
<h1 class="text-3xl sm:text-4xl font-bold text-slate-800 tracking-tight">Lager-System</h1>
|
||||
<p class="text-slate-500 mt-2">Bitte wählen Sie Ihren Namen aus.</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<form action="/WarehouseShippingNote" method="POST">
|
||||
<input type="hidden" name="wantedUserId" :value="selectedUserId"/>
|
||||
|
||||
<div class="mb-5 p-1.5 bg-slate-100 rounded-xl flex flex-wrap justify-center gap-1.5">
|
||||
<button v-for="letter in availableLetters" :key="letter" type="button"
|
||||
@click="selectedLetter = letter; selectedUserId = null;"
|
||||
class="w-12 h-12 rounded-lg text-base font-bold transition-all duration-300"
|
||||
:class="selectedLetter === letter ? 'bg-brand-blue text-white shadow' : 'bg-transparent text-slate-500 hover:bg-slate-200'">
|
||||
{{ letter === 'All' ? 'Alle' : letter }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="user-list-container max-h-[40vh] min-h-[150px] overflow-y-auto relative p-1.5">
|
||||
<TransitionGroup name="list" tag="div" class="relative overflow-x-hidden">
|
||||
<button v-for="(user, index) in filteredUsers" :key="user.value" type="button"
|
||||
@click="selectUser(user.value)"
|
||||
class="w-full text-left p-4 rounded-xl transition-all duration-200 ease-in-out mb-2 last:mb-0"
|
||||
:style="{ 'transition-delay': (index * 15) + 'ms' }"
|
||||
:class="selectedUserId === user.value ? 'bg-brand-blue text-white shadow-lg' : 'bg-slate-50 hover:bg-slate-200 text-slate-700'">
|
||||
<span class="text-lg font-medium">{{ user.text }}</span>
|
||||
</button>
|
||||
</TransitionGroup>
|
||||
<div v-if="!filteredUsers.length" class="text-center text-slate-400 pt-10">
|
||||
<p>Keine Mitarbeiter für diese Auswahl.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<button type="submit" :disabled="!selectedUserId"
|
||||
class="w-full text-white font-bold py-3.5 px-4 rounded-xl text-xl transition-all duration-300 bg-brand-blue hover:bg-blue-600 focus:outline-none focus:ring-4 focus:ring-blue-500/50 disabled:bg-slate-300 disabled:text-slate-500 disabled:cursor-not-allowed">
|
||||
Anmelden
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
</div>
|
||||
</Transition>
|
||||
`
|
||||
}).mount('#app');
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,7 +15,7 @@
|
||||
</footer>
|
||||
<!-- end Footer -->
|
||||
|
||||
<?php if(defined("ENABLE_VODIA_IDENTITY_SWITCHER") && ENABLE_VODIA_IDENTITY_SWITCHER && $me->is("employee")): ?>
|
||||
<?php if(defined("ENABLE_VODIA_IDENTITY_SWITCHER") && ENABLE_VODIA_IDENTITY_SWITCHER && $me->is("employee") && !isset($_SESSION[MFAPPNAME . '_warehouse_login_override'])): ?>
|
||||
<script type="text/javascript" src="<?=self::getResourcePath()?>assets/js/xinon-vodia-identity.js?<?=$git_merge_ts?>" defer></script>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
@@ -4,13 +4,36 @@ class AddressController extends mfBaseController {
|
||||
private $filter;
|
||||
|
||||
protected function init() {
|
||||
$this->needlogin = true;
|
||||
$me = new User();
|
||||
$me->loadMe();
|
||||
$this->me = $me;
|
||||
$this->layout()->set("me", $me);
|
||||
$user = null;
|
||||
$overrideKey = MFAPPNAME . '_warehouse_login_override';
|
||||
$timestampKey = MFAPPNAME . '_warehouse_login_override_timestamp';
|
||||
|
||||
if (!$me->is(["Admin", "salespartner"])) {
|
||||
$overrideId = $_SESSION[$overrideKey] ?? null;
|
||||
$timestamp = $_SESSION[$timestampKey] ?? null;
|
||||
|
||||
if (is_numeric($overrideId) && $timestamp && (time() - $timestamp) < 600) {
|
||||
$potentialUser = new User($overrideId);
|
||||
if ($potentialUser->id && $potentialUser->address_id == 1) {
|
||||
$user = $potentialUser;
|
||||
} else {
|
||||
unset($_SESSION[$overrideKey], $_SESSION[$timestampKey]);
|
||||
$this->redirect('WarehouseShippingNote');
|
||||
return;
|
||||
}
|
||||
} elseif ($overrideId) {
|
||||
unset($_SESSION[$overrideKey], $_SESSION[$timestampKey]);
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
$this->needlogin = true;
|
||||
$user = new User();
|
||||
$user->loadMe();
|
||||
}
|
||||
|
||||
$this->me = $user;
|
||||
$this->layout()->set("me", $this->me);
|
||||
|
||||
if (!$this->me->is(["Admin", "salespartner"])) {
|
||||
$this->redirect("Dashboard");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ class UserModel
|
||||
$where .= " AND WorkerPermission.employee = 'true'";
|
||||
}
|
||||
}
|
||||
if (isset($filter['active']))
|
||||
$where .= " AND Worker.active = " . (int)$filter['active'];
|
||||
|
||||
//var_dump($filter, $where);exit;
|
||||
return $where;
|
||||
|
||||
@@ -28,7 +28,7 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']],];
|
||||
|
||||
protected array $defaultOrder = ['key' => 'create', 'order' => 'DESC'];
|
||||
protected array $additionalJSVariables = ['WAREHOUSE_ADMIN' => true];
|
||||
protected array $additionalJSVariables = ['WAREHOUSE_ADMIN' => true, 'HIDE_MENU' => false];
|
||||
protected array $infoMessages = ['create' => 'Lieferschein wurde erstellt.',
|
||||
'update' => 'Lieferschein wurde aktualisiert',
|
||||
'delete' => 'Lieferschein wurde gelöscht',
|
||||
@@ -37,6 +37,7 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
|
||||
protected function prepareCrudConfig() {
|
||||
if (!$this->user->can('WarehouseAdmin')) $this->additionalJSVariables['WAREHOUSE_ADMIN'] = false;
|
||||
if (isset($_SESSION[MFAPPNAME . '_warehouse_login_override']) && is_numeric($_SESSION[MFAPPNAME . '_warehouse_login_override'])) $this->additionalJSVariables['HIDE_MENU'] = true;
|
||||
}
|
||||
|
||||
protected function beforeCreate($postData): bool {
|
||||
|
||||
@@ -27,21 +27,26 @@ class TTCrud extends mfBaseController {
|
||||
}
|
||||
|
||||
protected function init() {
|
||||
$this->needlogin = true;
|
||||
$me = new User();
|
||||
$me->loadMe();
|
||||
$this->user = $me;
|
||||
$this->layout()->set('me', $me);
|
||||
$className = get_class($this);
|
||||
|
||||
if (isset($this->permissionCheck) && !$me->can($this->permissionCheck)) {
|
||||
$this->redirect("Dashboard");
|
||||
} else if (!$me->can($this->permissionCheck) && !$me->is(["Admin"])) {
|
||||
$this->redirect("Dashboard");
|
||||
if (defined('TT_WAREHOUSE_LOGIN_OVERRIDE') && is_array(TT_WAREHOUSE_LOGIN_OVERRIDE)
|
||||
&& in_array($className, TT_WAREHOUSE_LOGIN_OVERRIDE) && !mfLoginController::isLoggedIn())
|
||||
$this->user = $this->loginOverride();
|
||||
else {
|
||||
$this->needlogin = true;
|
||||
$this->user = new User();
|
||||
$this->user->loadMe();
|
||||
}
|
||||
$this->layout()->set('me', $this->user);
|
||||
|
||||
$c = get_class($this);
|
||||
foreach ([str_replace('Controller', 'Model', $c), str_replace('Controller', '', $c)] as $m)
|
||||
if (class_exists($m)) {
|
||||
if (method_exists($this, 'permissionsCheckOverride'))
|
||||
$this->permissionsCheckOverride();
|
||||
else if (!$this->user->is(["Admin"]))
|
||||
$this->redirect("Dashboard");
|
||||
|
||||
|
||||
foreach ([str_replace('Controller', 'Model', $className), str_replace('Controller', '', $className)] as $m) {
|
||||
if (class_exists($m))
|
||||
$this->model = new $m();
|
||||
break;
|
||||
}
|
||||
@@ -52,6 +57,37 @@ class TTCrud extends mfBaseController {
|
||||
if (method_exists($this, 'afterInit')) $this->afterInit();
|
||||
}
|
||||
|
||||
protected function loginOverride() {
|
||||
$allowedIPs = ['193.105.204.200', '91.227.230.253', '193.105.204.195', '172.18.0.1'];
|
||||
if (!in_array($_SERVER['REMOTE_ADDR'], $allowedIPs)) $this->redirect('Dashboard');
|
||||
|
||||
if (isset($_POST['wantedUserId']) && is_numeric($_POST['wantedUserId'])) {
|
||||
$user = new User($_POST['wantedUserId']);
|
||||
if ($user->id && $user->address_id == 1) {
|
||||
$_SESSION[MFAPPNAME . '_warehouse_login_override'] = $user->id;
|
||||
$_SESSION[MFAPPNAME . '_warehouse_login_override_timestamp'] = time();
|
||||
$this->redirect('WarehouseShippingNote');
|
||||
}
|
||||
$this->redirect('Dashboard');
|
||||
}
|
||||
|
||||
$sessionUserId = $_SESSION[MFAPPNAME . '_warehouse_login_override'] ?? null;
|
||||
$sessionTimestamp = $_SESSION[MFAPPNAME . '_warehouse_login_override_timestamp'] ?? 0;
|
||||
|
||||
if (is_numeric($sessionUserId) && (time() - $sessionTimestamp <= 300)) {
|
||||
$user = new User($sessionUserId);
|
||||
if ($user->id && $user->address_id == 1) return $user;
|
||||
$this->redirect('WarehouseShippingNote');
|
||||
}
|
||||
|
||||
$users = UserModel::search(['employee' => true, 'active' => true]);
|
||||
$userOptions = array_map(fn($user) => ['value' => (int)$user->id, 'text' => $user->name], $users);
|
||||
|
||||
$this->layout()->set('userOptions', $userOptions);
|
||||
$this->layout()->setTemplate("VueViews/WarehouseLoginOverride");
|
||||
echo $this->layout()->render();
|
||||
exit;
|
||||
}
|
||||
/**
|
||||
* Returns the checkArray for the CRUD component.
|
||||
* @return array
|
||||
|
||||
@@ -128,7 +128,7 @@ class mfLoginController extends mfBaseController
|
||||
UserToken::checkToken();
|
||||
|
||||
|
||||
if ($_SESSION[MFAPPNAME . '_username'] && $_SESSION[MFAPPNAME . '_ip']) {
|
||||
if (isset($_SESSION[MFAPPNAME . '_username']) && $_SESSION[MFAPPNAME . '_username'] && $_SESSION[MFAPPNAME . '_ip']) {
|
||||
$username = $_SESSION[MFAPPNAME . '_username'];
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
$sid = session_id();
|
||||
|
||||
BIN
public/assets/images/xinon-sm-192.png
Normal file
BIN
public/assets/images/xinon-sm-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
public/assets/images/xinon-sm-512.png
Normal file
BIN
public/assets/images/xinon-sm-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
22
public/assets/pwa/shipping-note-tablet-manifest.json
Normal file
22
public/assets/pwa/shipping-note-tablet-manifest.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "Lieferscheine",
|
||||
"short_name": "Lieferscheine",
|
||||
"description": "XINON Lieferscheine App",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#f1f5f9",
|
||||
"theme_color": "#0055FF",
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/images/xinon-sm-192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/assets/images/xinon-sm-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
]
|
||||
}
|
||||
32
public/assets/pwa/shipping-note-tablet-sw.js
Normal file
32
public/assets/pwa/shipping-note-tablet-sw.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const CACHE_NAME = 'lager-system-cache-v1';
|
||||
const urlsToCache = [
|
||||
'.',
|
||||
'https://cdn.jsdelivr.net/npm/vue@3.4.27/dist/vue.global.min.js',
|
||||
'https://cdn.tailwindcss.com',
|
||||
'/assets/images/xinon-full.png',
|
||||
'/assets/icons/icon-192x192.png',
|
||||
'/assets/icons/icon-512x512.png'
|
||||
];
|
||||
|
||||
self.addEventListener('install', event => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then(cache => cache.addAll(urlsToCache))
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(response => response || fetch(event.request))
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', event => {
|
||||
const cacheWhitelist = [CACHE_NAME];
|
||||
event.waitUntil(
|
||||
caches.keys().then(cacheNames => Promise.all(
|
||||
cacheNames
|
||||
.filter(cacheName => !cacheWhitelist.includes(cacheName))
|
||||
.map(cacheName => caches.delete(cacheName))
|
||||
))
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user