Files
BeWoPlaner/BeWoPlanerMobil/Views/Main/SingleBookingPartial.cshtml
2026-06-25 02:08:47 +02:00

1148 lines
61 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@using BeWoPlanerMobil.Models
@using BeWoPlanerMobil.Util
@using BS.Shared
@using BS.Shared.Extensions
@model MainModel
@{
if(TempData[TempDataConstants.DoLogoutKey] is true)
{
<text>
<script type="text/javascript">
$("#second-logout-form").submit();
</script>
</text>
}
}
<script type="text/javascript">
window.addEventListener("load", function() {
@if(TempData[TempDataConstants.SupportConceptRelSelectedKey] is true)
{
<text>
window.updateCategory("@Model.BookingModePrefix", @Model.SelectedServiceCategoryOid);
window.updateServiceDescription("@Model.BookingModePrefix", @Model.SelectedServiceDescriptionOid);
window.updateEmployeeOids("@Model.BookingModePrefix", $("#sb-employee-input").val());
window.updateStartDate("@Model.BookingModePrefix", $(`#@Model.BookingModePrefix-start-date-input`).val());
window.updateStartTime("@Model.BookingModePrefix", $(`#@Model.BookingModePrefix-start-time-input`).val());
window.updateEndDate("@Model.BookingModePrefix", $(`#@Model.BookingModePrefix-end-date-input`).val());
window.updateEndTime("@Model.BookingModePrefix", $(`#@Model.BookingModePrefix-end-time-input`).val());
window.updateDurationUnit("sb", ($("#sb-hours-minutes-dropdown-btn").text().trim() === "Stunden" ? "h" : "m"));
</text>
}
});
$(document).ready(function() {
const dropdownBtn = $("#sb-hours-minutes-dropdown-btn");
dropdownBtn.on("click", function() {
dropdownBtn.data("text", dropdownBtn.text().replaceAll(/\s/g, ""));
});
$(".hours-minutes-link").on("click", function () {
const previousText = dropdownBtn.data("text");
const currentText = $(this).text().replaceAll(/\s/g, "");
if(previousText === currentText) {
return;
}
const buttonText = $(this).text();
dropdownBtn.text(buttonText);
window.setInputFilter();
window.updateDurationUnit("sb", (buttonText === "Stunden" ? "h" : "m"));
$.get('@Url.Action("ToggleDurationUnit")', { isInHoursMode: buttonText === "Stunden" });
calcDuration("sb", true, false, false);
});
@{
if(Model.SelectedSupportConcept != null || Model.SelectedCostBearerSupportConceptOid == -2)
{
<text>
initializeIntervalSelection();
</text>
}
}
window.calcPrependWidth();
});
function deleteSigntarue(signatureOid, serviceRecordOid, customerName, dateOfRecord) {
showMessagePopupWithCallback("Unterschrift löschen",
`Sind Sie sicher, dass Sie die Unterschrift von ${customerName} vom ${dateOfRecord} löschen möchten?`,
function () {
$("#sr-signature-to-del-oid-input").val(signatureOid);
$("#sr-sig-to-del-record-oid-input").val(serviceRecordOid);
showSpinner();
$("#delete-signature-form").submit();
},
false);
}
function resetSingleBookingForm() {
showSpinner();
$("#singleBookingForm").trigger("reset");
if($("#has-any-rating-types").val().toLowerCase() === "true") {
$(".goal-rating-badge").text("Bewerten");
} else {
$(".goal-rating-badge").text("");
}
$("#reset-single-booking-form-form").submit();
}
function setSelectedEmployeeForSingleBooking(selectedListElement, employeeOid) {
if(employeeOid !== null && employeeOid !== undefined && $.isNumeric(employeeOid) === true && employeeOid > 0) {
window.updateEmployeeOids('sb', employeeOid);
$.get("@Url.Action("SetServiceRecordEmployee")", {employeeOid: parseFloat(employeeOid)}, function (employeeName) {
$("#single-booking-employee-modal-popup").parent().find(".dropdown-toggle").text(employeeName);
$("#single-booking-employee-modal-popup").modal("hide");
window.loadServiceRecords();
});
}
}
function initializeIntervalSelection() {
const selectedOption = $("#timeFrameSelect option:selected").val();
switch (selectedOption) {
case "0":
$("#last-x-interval-container, #custom-interval-container, #month-interval-container").hide();
break;
case "1":
$("#last-x-interval-container, #custom-interval-container, #month-interval-container").hide();
break;
case "2":
$("#last-x-interval-container, #custom-interval-container, #month-interval-container").hide();
break;
case "3":
$("#last-x-interval-container, #custom-interval-container, #month-interval-container").hide();
break;
case "4":
$("#last-x-interval-container, #custom-interval-container, #month-interval-container").hide();
break;
case "5":
$("#last-x-interval-container").show();
$("#custom-interval-container, #month-interval-container").hide();
break;
case "6":
$("#custom-interval-container").show();
$("#last-x-interval-container, #month-interval-container").hide();
break;
case "7":
$("#month-interval-container").show();
$("#last-x-interval-container, #custom-interval-container").hide();
break;
}
}
function changeServiceRecordIntervalSelection(select) {
const selectedOption = $(select).val();
showSpinner();
if(["0", "1", "2", "3", "4"].includes(selectedOption)) {
$("#last-x-interval-container, #custom-interval-container, #month-interval-container").hide();
}
else if(selectedOption === "5") {
$("#last-x-interval-container").show();
$("#custom-interval-container, #month-interval-container").hide();
}
else if(selectedOption === "6") {
$("#custom-interval-container").show();
$("#last-x-interval-container, #month-interval-container").hide();
}
else if(selectedOption === "7") {
$("#month-interval-container").show();
$("#last-x-interval-container, #custom-interval-container").hide();
}
window.loadServiceRecords();
}
</script>
<script type="text/javascript">
var _externalCallPollingTimer = null;
// Zuletzt verwendeter Modus wird aus der Session initialisiert
var _aiVoiceMode = '@Model.AiVoiceMode';
function startExternalCall() {
// Direkt starten mit dem gespeicherten Modus, keine Moduswahl anzeigen
if (_aiVoiceMode === '1') {
$('#external-call-status-text').text('Um die Aufnahme zu starten, klicken Sie auf den Link, den wir an Ihren ownChat Account gesendet haben.');
}
else {
$('#external-call-status-text').text('Bitte wechseln Sie zu ownChat um die Aufnahme zu starten.\nHINWEIS: Wenn ownChat auf diesem Gerät nicht installiert ist, ist die Spracheingabe nicht möglich.');
}
document.getElementById('external-call-overlay').style.display = 'flex';
$.ajax({
url: '@Url.Action("GetExternalCallUrl", "Main")',
type: 'GET',
success: function(result) {
if (result && result.success) {
// Custom URL Scheme (ownchat://) per temporärem Anchor-Element öffnen
if (result.url && result.url.length > 0) {
var $a = $('<a>').attr('href', result.url).css('display', 'none').appendTo('body');
$a[0].click();
$a.remove();
}
// Polling starten - prüft regelmäßig ob der Callback aufgerufen wurde
_externalCallPollingTimer = setInterval(function() {
$.ajax({
url: '@Url.Action("CheckExternalCallStatus", "Main")',
type: 'GET',
success: function(pollResult) {
if (pollResult && pollResult.completed) {
stopExternalCallPolling();
onExternalCallCompleted(pollResult.data);
} else if (pollResult && pollResult.statusMessage) {
$('#external-call-status-text').text(pollResult.statusMessage);
}
},
error: function() {
// Bei Fehler weiter pollen
}
});
}, 3000);
} else {
document.getElementById('external-call-overlay').style.display = 'none';
var errorMsg = (result && result.message) ? result.message : 'Die externe URL konnte nicht ermittelt werden.';
showAlertMessageBox(errorMsg);
}
},
error: function() {
document.getElementById('external-call-overlay').style.display = 'none';
showAlertMessageBox('Fehler bei der Kommunikation mit dem Server.');
}
});
}
function openAiVoiceSettings() {
// Aktuellen Modus im Einstellungs-Overlay vorauswählen
$('input[name="aiVoiceMode"][value="' + _aiVoiceMode + '"]').prop('checked', true);
document.getElementById('aivoice-settings-overlay').style.display = 'flex';
}
function closeAiVoiceSettings() {
document.getElementById('aivoice-settings-overlay').style.display = 'none';
}
function saveAiVoiceMode() {
var selectedMode = $('input[name="aiVoiceMode"]:checked').val();
$.ajax({
url: '@Url.Action("SetAiVoiceMode", "Main")',
data: { mode: selectedMode },
type: 'POST',
success: function() {
_aiVoiceMode = selectedMode;
closeAiVoiceSettings();
cancelExternalCall();
showMessagePopup('Einstellungen gespeichert', 'Die Änderungen wurden übernommen. Bitte starten Sie die Aufnahme erneut.');
},
error: function() {
showAlertMessageBox('Fehler beim Speichern der Einstellung.');
}
});
}
function cancelExternalCall() {
stopExternalCallPolling();
// Server informieren, dass abgebrochen wurde callId liegt serverseitig in der Session
$.ajax({
url: '@Url.Action("CancelExternalCall", "Main")',
type: 'POST'
});
}
function stopExternalCallPolling() {
if (_externalCallPollingTimer) {
clearInterval(_externalCallPollingTimer);
_externalCallPollingTimer = null;
}
document.getElementById('external-call-overlay').style.display = 'none';
}
function onExternalCallCompleted(data) {
var $target;
var targetIndex;
// Prüfen ob die Tab-Ansicht sichtbar ist (mehrere Dokutypes vorhanden)
var $activeTabTextarea = $('#dokufelderTab .tab-pane.active textarea');
if ($activeTabTextarea.length > 0) {
// Das aktive Tab-Textarea verwenden; Index aus der ID auslesen (sb-doku-textarea-{i})
$target = $activeTabTextarea;
targetIndex = parseInt($target.attr('id').replace('sb-doku-textarea-', ''), 10);
} else {
// Fallback: Einzelfeld Dokumentation6
$target = $('#sb-doku-textarea-5');
targetIndex = 5;
}
if ($target.length > 0) {
$target.val(data);
window.updateDokuText('sb', data, targetIndex);
}
}
</script>
@using(Html.BeginForm("ResetSingleBookingForm", "Main", FormMethod.Post, new { Id = "reset-single-booking-form-form" })) { }
<div class="row">
@* ----- Einzelbuchungsformular ----- *@
<div class="col col-12 col-md-5 booking-form-container">
@if (Model != null)
{
using (Html.BeginForm("SelectSupportConcept", "Main", FormMethod.Post, new { Id = "selectSupportConceptForm", onreset = "resetSingleBookingForm()" }))
{
var selectedSuportConceptTimeSpanColorClass = (Model.SelectedSupportConceptListObject?.IsAboutToExpire ?? false)
? "text-bewo-is-about-to-expire"
: (Model.SelectedSupportConceptListObject?.ExpiresInThreeMonthsOrLess ?? false)
? "text-bewo-expires-in-three-months"
: "text-light";
var isFormDisabled = Model.SelectedCostBearerSupportConceptOid.HasValue ? string.Empty : "disabled";
var nameAndDateOfBirth = Model.SelectedSupportConceptListObject?.NameAndDateOfBirth ?? string.Empty;
var supportConceptTimeSpan = Model.SelectedSupportConceptListObject?.SupportConceptTimeSpan ?? string.Empty;
<div>
<div class="container p-0">
<div class="row">
<div class="col mt-3">
<div class="list-group">
<a href="#" class="list-group-item flex-column align-items-start no-underline bg-bewo-task-red" data-toggle="modal" data-target="#supportConceptList">
<div class="d-flex">
<h5 class="mb-1 text-light text-left" id="selectedSupportConceptCustomerInfo">@nameAndDateOfBirth</h5>
</div>
@if(Model.SelectedCostBearerSupportConceptOid > 0)
{
<div class="dropdown-divider my-0 py-0"></div>
}
<p class="mb-1 text-left small @selectedSuportConceptTimeSpanColorClass" id="selectedSupportConceptInfo">
@supportConceptTimeSpan
</p>
</a>
</div>
</div>
@if(Model.SelectedCostBearerSupportConceptOid is > 0)
{
<div class="col-md-auto mt-3">
<button type="button" class="btn btn-outline-primary w-100 h-100" id="statistics-button" data-toggle="modal" data-target="#statistics-popup" onclick="showPieChart()">
<span class="d-md-none align-middle">Statistik</span>
<span class="fas fa-chart-pie align-middle" style="font-size: 2rem;"></span>
</button>
</div>
}
</div>
</div>
</div>
@* Hilfeplanauswahlpopup *@
<div class="modal" role="dialog" tabindex="-1" id="supportConceptList">
<div class="modal-dialog text-center modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title font-weight-bold">Hilfeplanauswahl</h5>
<button type="button" class="close" data-dismiss="modal" onclick="resetSupportConceptSearch()">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
@* Hilfeplansuche *@
<div class="input-group mb-3">
<input class="form-control w-100" autocomplete="on" type="text" placeholder="Suchen..." oninput="supportConceptSearchOnChange()" id="support-concept-search-input" />
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" onclick="resetSupportConceptSearch()">
<span class="fas fa-times-circle"></span>
</button>
</div>
</div>
<hr />
@* Hilfeplanliste *@
<div class="list-group">
@foreach (var supportConceptListObject in Model.SupportConceptListObjects)
{
var nameAndDoBColorClass = "text-dark";
var timeSpanColorClass = supportConceptListObject.IsAboutToExpire ?
"text-bewo-is-about-to-expire" :
supportConceptListObject.ExpiresInThreeMonthsOrLess ?
"text-bewo-expires-in-three-months" :
"text-dark";
if(supportConceptListObject.IsArchived)
{
timeSpanColorClass = "text-bewo-archived-sc";
nameAndDoBColorClass = timeSpanColorClass;
}
else if(supportConceptListObject.IsDeleted)
{
timeSpanColorClass = "text-bewo-deleted-sc";
nameAndDoBColorClass = timeSpanColorClass;
}
<a href="#" class="list-group-item flex-column align-items-start no-underline blubb-test" onclick="selectSupportConcept(@supportConceptListObject.CostBearer2SupportConceptOid)" data-dismiss="modal">
<div class="justify-content-between">
<h5 class="mb-1 text-dark text-left supportconcept-name-header @nameAndDoBColorClass">
@supportConceptListObject.NameAndDateOfBirth
</h5>
</div>
<p class="mb-1 text-left @timeSpanColorClass">
@supportConceptListObject.SupportConceptTimeSpan
</p>
</a>
}
</div>
</div>
</div>
</div>
</div>
@* /Hilfeplanauswahlpopup *@
<input type="hidden" name="CostBearer2SupportConceptOid" id="sb-cb2ScRelOid" value="@Model.SelectedSupportConceptListObject.CostBearer2SupportConceptOid">
}
if(Model.IsCustomerAbsence)
{
<div class="container my-1 mb-0 mx-auto p-0 w-100 text-center">
<span>
<i class="fas fa-exclamation-triangle text-warning"></i>
<div class="text-danger d-inline">Klient ist abwesend</div>
<p class="text-danger mb-0 pb-0">@Model.CustomerAbsenceInfo</p>
</span>
</div>
}
using(Html.BeginForm("CreateOrUpdateServiceRecord", "Main", FormMethod.Post, new { id = "singleBookingForm" }))
{
<div>
@* ----- Einzelbuchung: Mitarbeiter ----- *@
<div class="form-row">
<div class="col-md">
<div class="mt-2" id="single-booking-employee-container">
@Html.PopupWithSearchForIFilterables(Model.Employees, "setSelectedEmployeeForSingleBooking", null, Model.SelectedEmployee?.DetailDescription, "single-booking-employee-modal-popup", false)
@{
var sbEmployeeInputValue = string.Empty;
if (Model.SelectedEmployee?.EmployeeOid is not null)
{
sbEmployeeInputValue = $"value=\"{Model.SelectedEmployee.EmployeeOid}\"";
}
}
<input type="hidden" id="sb-employee-input" onchange="window.updateEmployeeOids('sb', $(this).val())" @sbEmployeeInputValue />
</div>
</div>
</div>
@* ----- Einzelbuchung: Ziele ----- *@
@if (Model.SelectedSupportConcept != null && Model.SelectedSupportConcept.Goals.Any())
{
<div class="form-row">
<div class="col-md mt-2">
<div class="card w-100">
<div class="card-header py-0" onclick="cardHeaderClick2(this)">
<div class="d-inline-flex">
<i class="fas @Html.GetCollapsibleChevron(Model, "goals-container") h-100 text-dark"></i>
</div>
<div class="d-inline-flex h-100">
<button type="button" class="btn btn-link text-dark">
Ziele
</button>
</div>
</div>
<div class="collapse @Html.GetCollapsibleClass(Model.Collapsibles2IsShown, "goals-container")" id="goals-container">
<div class="card-body p-1">
<div class="input-group input-group-sm mb-1">
<input type="text" class="form-control" placeholder="Suchen ..." id="goal-search-input" oninput="goalTreeSearchOnChangeV2($(this))" />
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" onclick="resetGoalTreeSearch()">
<span class="fas fa-times-circle"></span>
</button>
</div>
</div>
<div class="btn-group btn-group-sm mb-1 w-100" role="group">
<button type="button" class="btn btn-outline-primary" onclick="window.showAllGoals()">Alle auf</button>
<button type="button" class="btn btn-outline-primary" onclick="window.hideAllGoals()">Alle zu</button>
</div>
<div class="container p-0 m-0" id="goals-tree">
@Html.Partial("GoalTreePartial", Model)
</div>
</div>
</div>
</div>
</div>
</div>
}
@* ----- Einzelbuchung: Kategorie & Leistung ----- *@
<div id="category-to-description-container">
@Html.Partial("Category2ServiceDescriptionPartial", Model)
</div>
@* ----- Marker ----- *@
@if (Model.ShowMarker)
{
var markerCheckedValue = Model.SelectedServiceRecord?.ServiceRecordType == ServiceRecordTypeId.Content ? "checked" : string.Empty;
<div class="form-row">
<div class="col-md mt-1">
<div class=" custom-control custom-checkbox custom-control-inline my-0">
<input type="checkbox" class="custom-control-input" onchange="window.updateMarker('sb', $(this).val());" name="@FormCollectionConstants.MarkerKey" id="sb-marker-cb" @markerCheckedValue />
<label class="custom-control-label" for="sb-marker-cb">Marker</label>
</div>
</div>
</div>
}
@* ----- Betrag ----- *@
@if (Model.ShowBetrag)
{
var betrag = string.Empty;
if (Model.SelectedServiceRecord?.Betrag is > 0)
{
betrag = Model.SelectedServiceRecord.Betrag.Value.ToString("#.00");
}
<div class="form-row mt-1">
<div class="col-md">
<div class="form-group my-0">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text prepend-input-group-text">
Betrag
</div>
</div>
<input class="form-control" autocomplete="on" onchange="window.updateBetrag('sb', $(this).val())" type="text" id="sb-betrag" name="betrag" value="@betrag" />
<div class="input-group-append">
<div class="input-group-text">€</div>
</div>
</div>
</div>
</div>
</div>
}
<hr class="mt-2 mb-0" />
@* ----- Einzelbuchung: Startdatum und gegebenenfalls Enddatum ----- *@
@{
var divisor = Model.SelectedDurationUnit == "Stunden" ? 60 : 1;
var durationValue = Model.DurationToEdit;
var afterValidationState = TempData[TempDataConstants.AfterValidationStateKey];
if (Model.SetStartTimeToEndTimeAfterSave && !Model.IsInEditingMode && afterValidationState is false)
{
durationValue = Model.NewDuration?.ToString();
if (Model.NewDuration.HasValue)
{
durationValue = (Model.NewDuration / divisor).ToString();
}
}
var columnWidth = Model.IsEndDateVisible ? "col-md-6" : "col";
}
<div class="form-row">
<div class="@columnWidth mt-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text prepend-input-group-text">
Startdatum
</span>
</div>
<input class="form-control" type="date" name="Datum" id="sb-start-date-input" onchange="calcDuration('sb', false, true, false); window.updateStartDate('sb', $(this).val());" value="@Model.StartDateToEditJSFormat" />
</div>
</div>
@if (Model.IsEndDateVisible)
{
<div class="col-md-6 mt-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text prepend-input-group-text">
Enddatum
</span>
</div>
<input class="form-control" type="date" name="Enddatum" id="sb-end-date-input" onchange="calcDuration('sb', false, false, true); window.updateEndDate('sb', $(this).val());" value="@Model.EndDateToEditJSFormat" />
</div>
</div>
}
</div>
<div class="form-row">
@{
var von = Model.IsInTransferMode || Model.IsInEditingMode || afterValidationState is true ? Model.StartTimeToEdit : Model.NewStartTime;
var bis = Model.IsInTransferMode || Model.IsInEditingMode || afterValidationState is true ? Model.EndTimeToEdit : Model.NewEndTime;
var x = von;
}
<div class="col mt-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
Von
</span>
</div>
<input class="form-control" type="time" name="Start" id="sb-start-time-input" onchange="calcDuration('sb', false, true, false);window.updateStartTime('sb', $(this).val());" value="@von" />
<span class="input-group-text rounded-0 border-left-0 border-right-0">
Bis
</span>
<input class="form-control" type="time" name="Ende" id="sb-end-time-input" onchange="calcDuration('sb', false, false, true);window.updateEndTime('sb', $(this).val());" value="@bis" />
</div>
</div>
</div>
@* ----- Einzelbuchung: Dauer ----- *@
<div class="form-row mt-2">
<div class="col-md">
<div class="form-group my-0">
<div class="input-group">
<div class="input-group-prepend">
@{
var labelText = Model.EinheitZeiterfassung == 1 ? "Dauer (h)" : "Dauer";
}
<div class="input-group-text prepend-input-group-text" id="sb-duration-label">
@labelText
</div>
</div>
<input type="text" class="form-control" id="sb-duration-input" name="Dauer" onchange="beiDaueraenderung('sb'); window.updateDuration('sb', $(this).val());" value="@durationValue">
@if (Model.IsZeiterfassungInStdMin)
{
<div class="input-group-append">
<button id="sb-hours-minutes-dropdown-btn" class="btn btn-outline-primary dropdown-toggle w-100" type="button" data-toggle="dropdown">
@Model.SelectedDurationUnit
</button>
<div class="dropdown-menu">
<a class="dropdown-item hours-minutes-link" href="#">Minuten</a>
<a class="dropdown-item hours-minutes-link" href="#">Stunden</a>
</div>
</div>
}
</div>
</div>
</div>
</div>
@* ----- Einzelbuchung: Distanz ----- *@
@if (Model.ShowDistanceField)
{
<div class="form-row mt-2">
<div class="col-md">
<div class="form-group my-0">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
Gefahrene Kilometer
</div>
</div>
<input class="form-control" autocomplete="on" id="sb-distance-input" name="Distanz" onchange="validateDistanceInput(); window.updateDistance('sb', $(this).val());" value="@Model.DistanceToEdit">
</div>
</div>
</div>
</div>
}
@* ----- Einzelbuchung: Textbausteine ----- *@
@if (Model.HasRightToSeeTextModules)
{
<script type="text/javascript">
function textModuleButtonOnClick() {
$('#tree').load('@Url.Action("LoadUpToDateTextModules")');
}
</script>
<div class="form-row mt-2" id="textmodule-container">
<div class="col-md">
<button type="button" class="btn btn-primary form-control" data-toggle="modal" data-target="#textbausteine-popup" id="sb-textbausteine-btn" onclick="textModuleButtonOnClick()">Textbausteine</button>
</div>
</div>
}
@* ----- Einzelbuchung: KI Funktionen ----- *@
@if (Model.HasRightToSeeAiPromptsInZeiterfassung)
{
<script type="text/javascript">
</script>
<div class="form-row mt-2" id="aifunction-container">
<div class="col-md">
@if (Model.HasRightToUseAiVoice)
{
<button type="button" class="btn btn-primary float-right mr-1" id="aivoice-button" onclick="startExternalCall();" title="KI-Spracheingabe & Transkription" style="padding: 0 0.6em; overflow: hidden; line-height: 0;"><img src="@Url.Content("~/Content/images/Icon_Mikro_Weiss.png")" alt="KI-Spracheingabe & Transkription" style="height:2.25em; display: block;" /></button>
}
<button type="button" class="btn btn-primary float-right mr-1" id="aiprompt-btn" style="padding: 0 0.6em; overflow: hidden; line-height: 0;"><img src="@Url.Content("~/Content/images/Icon_KI_Funktion_Weiss+Gruen.png")" alt="KI-Promptbausteine auswählen" style="height:2.25em; display: block;" /></button>
</div>
</div>
<!-- KI-Promptbausteine Modal -->
<div class="modal fade" id="aiprompt-modal" tabindex="-1" role="dialog" aria-labelledby="aiprompt-modal-title" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="aiprompt-modal-title">KI-Promptbausteine auswählen</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Schließen">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row" style="min-height: 350px;">
<div class="col-md-5 border-right" id="aiprompt-tree-col">
<div id="aiprompt-tree" style="overflow-y: auto; max-height: 400px;">
<div class="text-muted small p-2">Wird geladen…</div>
</div>
</div>
<div class="col-md-7" id="aiprompt-prompts-col">
<div id="aiprompt-prompts-list">
<div class="text-muted small p-2">Bitte einen Ordner auswählen.</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
(function () {
function buildTree(folders, container, level) {
folders.forEach(function (folder) {
var indent = level * 16;
var item = $('<div class="aiprompt-folder-item" style="cursor:pointer; padding: 4px 6px; border-radius:4px; margin-bottom:2px;">'
+ '<span style="display:inline-block; width:' + indent + 'px;"></span>'
+ '<span class="aiprompt-folder-toggle mr-1" style="display:inline-block; width:14px;">'
+ (folder.SubFolders && folder.SubFolders.length > 0 ? '&#9654;' : '&nbsp;')
+ '</span>'
+ '<span class="aiprompt-folder-label"><strong>' + $('<div>').text(folder.Title).html() + '</strong></span>'
+ '</div>');
var subContainer = null;
if (folder.SubFolders && folder.SubFolders.length > 0) {
subContainer = $('<div class="aiprompt-subfolders" style="display:none;"></div>');
buildTree(folder.SubFolders, subContainer, level + 1);
}
item.on('click', function (e) {
e.stopPropagation();
$('.aiprompt-folder-item').removeClass('bg-primary text-white').css('color', '');
item.addClass('bg-primary text-white');
if (subContainer) {
var expanded = subContainer.is(':visible');
subContainer.toggle(!expanded);
item.find('.aiprompt-folder-toggle').html(expanded ? '&#9654;' : '&#9660;');
}
showPrompts(folder.SubPrompts || []);
});
container.append(item);
if (subContainer) container.append(subContainer);
});
}
function showPrompts(prompts) {
var list = $('#aiprompt-prompts-list').empty();
if (!prompts || prompts.length === 0) {
list.html('<div class="text-muted small p-2">Keine Prompts in diesem Ordner.</div>');
return;
}
prompts.forEach(function (p) {
var card = $('<div class="card mb-2" style="cursor:pointer;"></div>');
var body = $('<div class="card-body p-2"></div>');
body.append($('<h6 class="card-title mb-1"></h6>').text(p.Title));
if (p.Description) body.append($('<p class="card-text small text-muted mb-0"></p>').text(p.Description));
card.append(body);
card.on('click', function () {
if (p.Oid == null) return;
// Prompt server-seitig ausführen und Ergebnis ins Doku-Feld schreiben
var $card = $(this);
$('#aiprompt-prompts-list .card').css('pointer-events', 'none').css('opacity', '0.5');
$card.css('opacity', '1').append(
'<div class="aiprompt-loading text-muted small mt-1">'
+ '<span class="spinner-border spinner-border-sm mr-1"></span>Wird ausgeführt …</div>');
$.ajax({
url: '@Url.Action("StartPrompt", "Main")',
type: 'POST',
data: { promptOid: p.Oid },
success: function (resp) {
$('#aiprompt-modal').modal('hide');
var text = resp && resp.text ? resp.text : '';
var activeTextarea = $('textarea:visible:first[id^="sb-doku-textarea"]');
if (activeTextarea.length && text) {
var current = activeTextarea.val();
activeTextarea.val(current ? current + '\n' + text : text).trigger('change');
}
},
error: function (xhr) {
var msg = 'Fehler beim Ausführen des Prompts.';
try {
var r = JSON.parse(xhr.responseText);
if (r && r.message) msg += '\n' + r.message;
} catch (e) { }
console.error('StartPrompt Fehler:', xhr.status, xhr.responseText);
$('.aiprompt-loading').remove();
$('#aiprompt-prompts-list .card').css('pointer-events', '').css('opacity', '');
alert(msg);
}
});
});
list.append(card);
});
}
var dataLoaded = false;
$('#aiprompt-btn').on('click', function () {
$('#aiprompt-modal').modal('show');
if (dataLoaded) return;
$.ajax({
url: '@Url.Action("GetAiPromptbausteinFolder", "Main")',
type: 'GET',
success: function (data) {
dataLoaded = true;
var tree = $('#aiprompt-tree').empty();
if (!data || data.length === 0) {
tree.html('<div class="text-muted small p-2">Keine Ordner verfügbar.</div>');
return;
}
buildTree(data, tree, 0);
},
error: function (xhr, textStatus, errorThrown) {
var details = 'Status: ' + xhr.status + ' ' + (xhr.statusText || '')
+ '\ntextStatus: ' + textStatus
+ '\nerror: ' + errorThrown;
if (xhr.responseText) {
details += '\n\nAntwort:\n' + xhr.responseText.substring(0, 4000);
}
console.error('GetAiPromptbausteinFolder Fehler:', details);
$('#aiprompt-tree').html(
'<div class="text-danger small p-2">Fehler beim Laden der Promptbausteine.<br/>'
+ '<pre style="white-space:pre-wrap; font-size:11px; max-height:320px; overflow:auto;">'
+ $('<div>').text(details).html()
+ '</pre></div>');
}
});
});
})();
</script>
}
@* ----- Einzelbuchung: Dokumentation ----- *@
<div class="form-row">
<div class="col-md mt-3">
@if (Model.NumberOfDokuTypes == 0)
{
<div class="form-group my-0">
<label for="sb-doku-textarea-5">Dokumentation</label>
<textarea class="form-control" autocomplete="on" style="overflow: scroll;" onchange="window.updateDokuText('sb', $(this).val(), 5)" name="Dokumentation6" id="sb-doku-textarea-5">@Model.NoticeToEdit</textarea>
</div>
}
else
{
<ul class="nav nav-tabs" role="tab-list">
@for (var i = 0; i < Model.Dokutypes.Length; i++)
{
var currentDokuType = Model.Dokutypes[i];
<li class="nav-item">
<a href="#sb-doku_@i" id="sb-doku_@i-tab" onchange="window.updateDokuText('sb', $(this).val(), @i)" data-toggle="tab" role="tab" class="nav-link@(i == 0 ? " active" : string.Empty)">
@currentDokuType.DisplayName
</a>
</li>
}
</ul>
<div class="tab-content" id="dokufelderTab">
@for (var i = 0; i < Model.Dokutypes.Length; i++)
{
var textareaName = i + 1 < 1 ? "Dokumentation" : "Dokumentation" + (i + 1);
<div class="tab-pane show@(i == 0 ? " active" : string.Empty)" id="sb-doku_@i" role="tabpanel">
<textarea class="form-control doku-form-control" onchange="window.updateDokuText('sb', $(this).val(), @i)" autocomplete="on" style="overflow: scroll;" id="sb-doku-textarea-@i" name="@textareaName">@Model.NoticeListToEdit[i]</textarea>
</div>
}
</div>
}
</div>
</div>
</div>
}
<div class="w-100 mt-2">
<button type="button" class="btn btn-primary float-right ml-1" id="create-button" onclick="submitSingleBookingForm()">@Model.SubmitButtonValue</button>
@if (Model.IsInEditingMode)
{
using (Html.BeginForm("ResetEditingMode", "Main", FormMethod.Post))
{
<button type="submit" class="btn btn-primary float-right" onclick="showSpinner()">Abbrechen</button>
}
}
else if (Model.IsInTransferMode)
{
using (Html.BeginForm("ResetTransferMode", "Main", FormMethod.Post))
{
<button type="submit" class="btn btn-primary float-right" onclick="showSpinner()">Abbrechen</button>
}
}
else
{
<button type="button" class="btn btn-primary float-right" id="reset-button" onclick="resetSingleBookingForm();">Abbrechen</button>
}
@Html.Partial("RestoreServiceRecordInputsPartial", Model)
</div>
@* Warte-Overlay für externen Aufruf *@
<div id="external-call-overlay" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:9999; justify-content:center; align-items:center;">
<div style="background:white; border-radius:8px; padding:30px; min-width:300px; text-align:center; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
<div class="mb-3">
<i class="fa fa-spinner fa-spin fa-2x"></i>
</div>
<p class="mb-3" id="external-call-status-text" style="white-space: pre-line">Bitte warten...</p>
<button type="button" class="btn btn-secondary mr-2" onclick="openAiVoiceSettings();">
<i class="fa fa-cog mr-1"></i>Einstellungen
</button>
<button type="button" class="btn btn-danger" onclick="cancelExternalCall();">Abbrechen</button>
</div>
</div>
@* Einstellungs-Overlay für Moduswahl *@
<div id="aivoice-settings-overlay" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); z-index:10000; justify-content:center; align-items:center;">
<div style="background:white; border-radius:8px; padding:30px; max-width:480px; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
<h6 class="mb-3">Modus für Spracheingabe</h6>
<div class="form-group text-left mb-4">
<div class="form-check mb-3">
<input class="form-check-input" type="radio" name="aiVoiceMode" id="aiVoiceModeDirect" value="0" />
<label class="form-check-label font-weight-bold" for="aiVoiceModeDirect">
Spracheingabe direkt aus Mobilklient starten
</label>
<small class="form-text text-muted">Diese Option setzt voraus, dass Mobilklient und ownChat auf gleichem Endgerät genutzt werden</small>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="aiVoiceMode" id="aiVoiceModePush" value="1" />
<label class="form-check-label font-weight-bold" for="aiVoiceModePush">
Spracheingabe per Push-Nachricht starten
</label>
<small class="form-text text-muted">Diese Option ermöglicht die Nutzung von Mobilklient und ownChat auf getrennten Endgeräten wie bei der Vollversion</small>
</div>
</div>
<div class="text-right">
<button type="button" class="btn btn-secondary mr-2" onclick="closeAiVoiceSettings();">Schließen</button>
<button type="button" class="btn btn-primary" onclick="saveAiVoiceMode();">Speichern</button>
</div>
</div>
</div>
}
</div>
@* /Einzelbuchungsformular *@
@* ----- Liste mit den Einträgen und dem Auswahl-Select ----- *@
<div class="col col-12 col-md-7">
@if(Model != null)
{
if(Model.SelectedSupportConcept != null || Model.SelectedCostBearerSupportConceptOid == -2)
{
var defaultStartDate = DateTime.Now.GetFirstOfMonth().ToString("yyyy-MM-dd");
var defaultEndDate = DateTime.Today.ToString("yyyy-MM-dd");
if (Model.SelectedZeitraum != null && Model.SelectedZeitraum.StartDate.HasValue && Model.SelectedZeitraum.EndDate.HasValue)
{
defaultStartDate = Model.SelectedZeitraum.StartDate.Value.ToString("yyyy-MM-dd");
defaultEndDate = Model.SelectedZeitraum.EndDate.Value.ToString("yyyy-MM-dd");
}
<div class="container-fluid w-100 mt-3 p-0">
<div class="form-row">
<div class="col">
<div class="form-group">
<select name="Records" class="custom-select" onchange="changeServiceRecordIntervalSelection(this)" id="timeFrameSelect">
<option value="5"
@{if(Model.ServiceRecordTimeInterval == ServiceRecordTimeInterval.XTage) { @Html.Raw("selected") }}>
Letzten x Tage
</option>
<option value="0"
@{if(Model.ServiceRecordTimeInterval == ServiceRecordTimeInterval.LastWeek) { @Html.Raw("selected") }}>
Letzten 7 Tage
</option>
<option value="1"
@{if(Model.ServiceRecordTimeInterval == ServiceRecordTimeInterval.LastMonth) { @Html.Raw("selected") }}>
Letzten 30 Tage
</option>
<option value="2"
@{if(Model.ServiceRecordTimeInterval == ServiceRecordTimeInterval.Last3Month) { @Html.Raw("selected") }}>
Letzten 90 Tage
</option>
<option value="3"
@{if(Model.ServiceRecordTimeInterval == ServiceRecordTimeInterval.All) { @Html.Raw("selected") }}>
Alle
</option>
<option value="6"
@{if(Model.ServiceRecordTimeInterval == ServiceRecordTimeInterval.ZeitraumWaehlen) { @Html.Raw("selected") }}>
Zeitraum wählen
</option>
<option value="7"
@{if(Model.ServiceRecordTimeInterval == ServiceRecordTimeInterval.Monatsauswahl) { @Html.Raw("selected") }}>
Monat wählen
</option>
</select>
</div>
</div>
<div class="col-auto">
<button type="button" class="btn btn-success w-100" onclick="loadServiceRecords()">
<span class="fas fa-sync-alt"></span>
</button>
</div>
</div>
@*----- Anzahl Tage: [Nummern-Input] ----- *@
<div class="form-row" id="last-x-interval-container">
<div class="col-12">
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Anzahl Tage:</span>
</div>
<input class="form-control" id="day-count-number" type="number" name="day-count-number" min="0" value="@Model.LastSelectedServiceRecordTimeIntervalDays" />
</div>
</div>
</div>
</div>
@*----- Zeitraum wählen: [Von-Datum] bis [Bis-Datum] erster des Monats bis aktuelles Datum ----- *@
<div class="form-row" id="custom-interval-container">
<div class="col-12">
<div class="form-group">
<div class="form-row">
<div class="col mt-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
Von
</span>
</div>
<input class="form-control" type="date" name="von" id="time-frame-start-date" onfocusout="loadServiceRecords()" value="@defaultStartDate"/>
<span class="input-group-text rounded-0 border-left-0 border-right-0">
Bis
</span>
<input class="form-control" type="date" name="bis" id="time-frame-end-date" onfocusout="loadServiceRecords()" value="@defaultEndDate"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-row" id="month-interval-container">
<div class="col-6">
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Monat</span>
</div>
<select name="month-select" class="custom-select" id="month-select" onchange="loadServiceRecords()">
<option value="1"
@{if(Model.LastSelectedServiceRecordMonth.Month == 1) { @Html.Raw("selected") } }>
Januar
</option>
<option value="2"
@{if(Model.LastSelectedServiceRecordMonth.Month == 2) { @Html.Raw("selected") } }>
Februar
</option>
<option value="3"
@{ if(Model.LastSelectedServiceRecordMonth.Month == 3) { @Html.Raw("selected") } }>
März
</option>
<option value="4"
@{if(Model.LastSelectedServiceRecordMonth.Month == 4) { @Html.Raw("selected") }}>
April
</option>
<option value="5"
@{if(Model.LastSelectedServiceRecordMonth.Month == 5) { @Html.Raw("selected") }}>
Mai
</option>
<option value="6"
@{if(Model.LastSelectedServiceRecordMonth.Month == 6) { @Html.Raw("selected") }}>
Juni
</option>
<option value="7"
@{if(Model.LastSelectedServiceRecordMonth.Month == 7) { @Html.Raw("selected") }}>
Juli
</option>
<option value="8"
@{if(Model.LastSelectedServiceRecordMonth.Month == 8) { @Html.Raw("selected") }}>
August
</option>
<option value="9"
@{if(Model.LastSelectedServiceRecordMonth.Month == 9) { @Html.Raw("selected") }}>
September
</option>
<option value="10"
@{if(Model.LastSelectedServiceRecordMonth.Month == 10) { @Html.Raw("selected") }}>
Oktober
</option>
<option value="11"
@{if(Model.LastSelectedServiceRecordMonth.Month == 11) { @Html.Raw("selected") }}>
November
</option>
<option value="12"
@{if(Model.LastSelectedServiceRecordMonth.Month == 12) { @Html.Raw("selected") }}>
Dezember
</option>
</select>
</div>
</div>
</div>
<div class="col-6">
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Jahr</span>
</div>
<select class="custom-select" name="year-select" id="year-select" onchange="loadServiceRecords()">
@{
var heuer = DateTime.Today.Year;
for(var year = heuer + 1; year >= heuer - 10; year--)
{
<option value="@year"
@{if(Model.LastSelectedServiceRecordMonth.Year == year) { @Html.Raw("selected") }}>
@year
</option>
}
}
</select>
</div>
</div>
</div>
</div>
</div>
}
<hr class="mt-2"/>
<div class="container-fluid mt-3" id="service-record-list-container">
@Html.Partial("ServiceRecordListPartial", Model)
</div>
}
</div>
@* ----- /Liste mit den Einträgen und dem Auswahl-Select ----- *@
</div>
@using(Html.BeginForm("DeleteSignature", "Main", FormMethod.Post, new { id = "delete-signature-form" }))
{
<input type="hidden" name="sr-signature-to-del-oid" id="sr-signature-to-del-oid-input" />
<input type="hidden" name="sr-sig-to-del-record-oid" id="sr-sig-to-del-record-oid-input" />
}