Merge branch 'RMLWorkorder/add-oaid-fcp-filter' into 'master'

Rml workorder/add oaid fcp filter

See merge request fronk/thetool!1723
This commit is contained in:
Luca Haid
2025-09-08 08:37:36 +00:00

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<link rel="shortcut icon" href="<?= self::getResourcePath() ?>assets/images/favicon.ico">
<title>Workorders</title>
<link rel="shortcut icon" href="/assets/images/favicon.ico">
<link rel="manifest" href="/js/pages/WorkorderBase/manifest.json">
<meta name="theme-color" content="#005384">
@@ -78,6 +79,7 @@
const workorders = ref([]);
const selectedWorkorder = ref(null);
const isLoading = ref(true);
const isDetailsLoading = ref(false);
const isDetailsPanelOpen = ref(false);
const searchTerm = ref('');
const documentation = reactive({ docs: [], journals: [] });
@@ -94,6 +96,7 @@
const isStandalone = ref(false);
const selectedFcp = ref('all');
const isFcpSelectOpen = ref(false);
const fcpSearchTerm = ref('');
const API_BASE_URL = window.TT_CONFIG.BASE_PATH || '/WorkorderCompany';
@@ -109,6 +112,16 @@
return [{ value: 'all', text: 'Alle FCPs' }, ...options];
});
const filteredFcpOptions = computed(() => {
if (!fcpSearchTerm.value) {
return fcpOptions.value;
}
const lowerCaseSearch = fcpSearchTerm.value.toLowerCase();
return fcpOptions.value.filter(option =>
option.text.toLowerCase().includes(lowerCaseSearch)
);
});
const selectedFcpText = computed(() => {
const selectedOption = fcpOptions.value.find(opt => opt.value === selectedFcp.value);
return selectedOption ? selectedOption.text : 'Alle FCPs';
@@ -201,6 +214,7 @@
};
const fetchDetails = async (workorderId) => {
isDetailsLoading.value = true;
try {
const [docRes, configRes] = await Promise.all([
api.get(`/getDocumentation?workorderId=${workorderId}`),
@@ -212,6 +226,7 @@
tenantConfig.value = configRes.data;
}
} catch (e) { console.error("Could not load details", e); }
finally { isDetailsLoading.value = false; }
};
const openDetails = (workorder) => {
@@ -342,17 +357,26 @@
isFcpSelectOpen.value = false;
};
onMounted(() => {
fetchWorkorders();
isStandalone.value = window.matchMedia('(display-mode: standalone)').matches;
});
// Lock body scroll when any modal is open
watch([isDetailsPanelOpen, isFcpSelectOpen, uploadModal, problemModal, fullscreenViewer, installModal], ([details, fcp, upload, problem, viewer, install]) => {
const isAnyModalOpen = details || fcp || upload.show || problem.show || viewer.show || install.show;
if (isAnyModalOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
}, { deep: true });
return {
isLoading, filteredWorkorders, searchTerm, isDetailsPanelOpen, selectedWorkorder, documentation, tenantConfig,
isLoading, isDetailsLoading, filteredWorkorders, searchTerm, isDetailsPanelOpen, selectedWorkorder, documentation, tenantConfig,
tempAdditionalInfo, isEditingInfo, newJournalEntry, uploadModal, problemModal, isUploading, isChecklistComplete,
checklist, fullscreenViewer, missingTasksPopover, translatedDocs, filteredJournals, installModal, isStandalone,
selectedFcp, isFcpSelectOpen, fcpOptions, selectedFcpText, fetchWorkorders, fetchDetails,
selectedFcp, isFcpSelectOpen, fcpOptions, selectedFcpText, fcpSearchTerm, filteredFcpOptions,
openDetails, closeDetails, getStatusInfo, formatDate, googleMapsLink, startEditInfo, saveAdditionalInfo,
handleFileSelect, executeUpload, addJournalEntry, submitProblem, handleCompleteClick, selectFcp
};
@@ -397,9 +421,10 @@
<div class="flex-grow pr-2 min-w-0">
<p class="font-bold text-slate-800 break-words">#{{ wo.id }} | {{ wo.customerName || 'N/A' }}</p>
<p class="text-sm text-slate-500 break-words">{{ wo.street }} {{ wo.hausnummer }}, {{ wo.plz }} {{ wo.city }}</p>
<div class="items-center text-xs text-slate-400 mt-1">
<span class="truncate">FCP: {{ wo.rimo_fcp_name || 'N/A' }}</span><br>
<div class="flex items-center text-xs text-slate-400 mt-1 flex-wrap">
<span class="mr-2">ID: {{ wo.id }}</span>
<span class="mr-2">OAID: {{ wo.oaid || 'N/A' }}</span>
<span class="truncate">FCP: {{ wo.rimo_fcp_name || 'N/A' }}</span>
</div>
</div>
<div class="flex-shrink-0 ml-2 text-right space-y-1">
@@ -465,14 +490,22 @@
<div class="bg-white p-4 rounded-lg border border-slate-200">
<h3 class="font-bold text-slate-700 mb-3">Checkliste</h3>
<ul v-if="checklist.length > 0" class="space-y-2">
<li v-for="item in checklist" :key="item.value" class="flex items-center text-sm">
<svg v-if="item.completed" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-green-500" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /></svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10" /></svg>
<span :class="{'text-slate-500 line-through': item.completed}">{{ item.text }}</span>
</li>
</ul>
<p v-else class="text-sm text-slate-500">Keine Checklisten-Einträge vorhanden.</p>
<div v-if="isDetailsLoading" class="space-y-3 animate-pulse">
<div v-for="i in 4" :key="i" class="flex items-center">
<div class="h-5 w-5 rounded-full bg-slate-200 mr-2"></div>
<div class="h-4 w-3/4 rounded bg-slate-200"></div>
</div>
</div>
<div v-else>
<ul v-if="checklist.length > 0" class="space-y-2">
<li v-for="item in checklist" :key="item.value" class="flex items-center text-sm">
<svg v-if="item.completed" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-green-500" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /></svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10" /></svg>
<span :class="{'text-slate-500 line-through': item.completed}">{{ item.text }}</span>
</li>
</ul>
<p v-else class="text-sm text-slate-500">Keine Checklisten-Einträge vorhanden.</p>
</div>
</div>
<div class="bg-white p-4 rounded-lg border border-slate-200">
@@ -502,7 +535,16 @@
<div class="bg-white p-4 rounded-lg border border-slate-200">
<h3 class="font-bold text-slate-700 mb-4">Journal</h3>
<div class="space-y-4 max-h-60 overflow-y-auto pr-2 journal-container">
<div v-if="isDetailsLoading" class="animate-pulse">
<div class="flex items-start">
<div class="flex-shrink-0 bg-slate-200 h-8 w-8 rounded-full mr-3"></div>
<div class="flex-grow space-y-2">
<div class="h-4 w-full rounded bg-slate-200"></div>
<div class="h-3 w-1/2 rounded bg-slate-200"></div>
</div>
</div>
</div>
<div v-else class="space-y-4 max-h-60 overflow-y-auto pr-2 journal-container">
<div v-if="filteredJournals.length === 0"><p class="text-sm text-slate-500">Keine Einträge.</p></div>
<div v-for="entry in filteredJournals" :key="entry.id" class="flex items-start">
<div class="flex-shrink-0 bg-secondary h-8 w-8 rounded-full flex items-center justify-center mr-3"><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" /></svg></div>
@@ -539,14 +581,20 @@
<!-- FCP Select Modal -->
<transition name="fade">
<div v-if="isFcpSelectOpen" class="fixed inset-0 z-30 flex items-end sm:items-center sm:justify-center" @click.self="isFcpSelectOpen = false">
<div class="bg-white rounded-t-lg sm:rounded-lg p-4 w-full max-w-sm flex flex-col max-h-[70vh]">
<div class="flex justify-between items-center mb-4 flex-shrink-0">
<div v-if="isFcpSelectOpen" class="fixed inset-0 z-30 flex items-end sm:items-center sm:justify-center p-4 sm:p-0" @click.self="isFcpSelectOpen = false">
<div class="bg-white rounded-t-lg sm:rounded-lg p-4 w-full max-w-sm flex flex-col max-h-[80vh] sm:max-h-[70vh]">
<div class="flex justify-between items-center mb-2 flex-shrink-0">
<h3 class="font-bold text-lg">FCP auswählen</h3>
<button @click="isFcpSelectOpen = false" class="p-1 rounded-full hover:bg-slate-100 text-2xl leading-none">&times;</button>
</div>
<div class="relative mb-2 flex-shrink-0">
<input type="text" v-model="fcpSearchTerm" placeholder="FCP suchen..." class="w-full p-2 pl-8 border border-slate-300 rounded-md">
<svg class="absolute left-2 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
</div>
<ul class="flex-grow overflow-y-auto -mr-2 pr-2">
<li v-for="option in fcpOptions" :key="option.value" @click="selectFcp(option.value)"
<li v-for="option in filteredFcpOptions" :key="option.value" @click="selectFcp(option.value)"
class="flex justify-between items-center p-3 rounded-md hover:bg-slate-100 cursor-pointer text-sm font-medium"
:class="{'bg-primary/10 text-primary': selectedFcp === option.value}">
<span>{{ option.text }}</span>
@@ -652,3 +700,4 @@
<script src="/js/pages/WorkorderBase/WorkorderServiceWorker.js"></script>
</body>
</html>