2025-03-13 19:50:56 -05:00

214 lines
9.9 KiB
PHP

{{--
Save this file to resources/views/components/soft-delete-conflict-modal.blade.php
or to the appropriate component location in your structure
--}}
@props(['title' => 'Record Already Exists'])
<div
x-data="{ show: false, conflict: null, formData: null, formElement: null, modelClass: null }"
x-show="show"
x-cloak
class="fixed inset-0 z-50 overflow-y-auto"
id="softDeleteConflictModal"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
x-on:open-soft-delete-modal.window="
show = true;
conflict = $event.detail.conflict;
formData = $event.detail.formData;
formElement = $event.detail.formElement;
modelClass = $event.detail.modelClass;
"
x-on:close-soft-delete-modal.window="show = false"
>
<div class="flex items-center justify-center min-h-screen p-4 text-center sm:p-0">
<div
x-show="show"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75"
aria-hidden="true"
@click="show = false"
></div>
<div
x-show="show"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6"
>
<div class="sm:flex sm:items-start">
<div class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto bg-yellow-100 rounded-full sm:mx-0 sm:h-10 sm:w-10">
<svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{{ $title }}
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
A record with the same unique identifier has been deleted previously. This might indicate a duplicate entry.
</p>
<div class="mt-4 p-3 bg-gray-50 rounded-lg">
<h4 class="text-sm font-medium text-gray-900">Deleted record details:</h4>
<div x-html="formatConflictDetails(conflict)" class="mt-2 text-sm text-gray-600"></div>
<div class="mt-1 text-xs text-gray-500">
Deleted at: <span x-text="conflict?.deleted_at || 'Unknown'"></span>
</div>
</div>
<p class="mt-4 text-sm text-gray-600">
You have the following options:
</p>
<ul class="mt-2 ml-5 list-disc text-sm text-gray-600">
<li>Restore the deleted record with your new values</li>
<li>Modify your current form input to avoid the conflict</li>
<li>Cancel the operation</li>
</ul>
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
class="inline-flex justify-center w-full px-4 py-2 text-base font-medium text-white bg-green-600 border border-transparent rounded-md shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm"
@click="resolveConflict()"
>
Restore Deleted Record
</button>
<button
type="button"
class="inline-flex justify-center w-full px-4 py-2 mt-3 text-base font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:w-auto sm:text-sm"
@click="show = false"
>
Modify Current Form
</button>
<button
type="button"
class="inline-flex justify-center w-full px-4 py-2 mt-3 mr-3 text-base font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:w-auto sm:text-sm"
@click="cancelOperation()"
>
Cancel
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('softDeleteConflictModal', () => ({
show: false,
conflict: null,
formData: null,
formElement: null,
modelClass: null,
formatConflictDetails(conflict) {
if (!conflict || !conflict.attributes) {
return 'No details available';
}
// Create a formatted HTML string with the conflict details
let html = '<dl class="grid grid-cols-2 gap-x-4 gap-y-2">';
// Skip internal fields like id, created_at, updated_at, deleted_at
const skipFields = ['id', 'created_at', 'updated_at', 'deleted_at'];
for (const [key, value] of Object.entries(conflict.attributes)) {
if (!skipFields.includes(key) && value !== null) {
html += `
<dt class="text-xs font-medium text-gray-500">${key}:</dt>
<dd class="text-xs text-gray-900">${value}</dd>
`;
}
}
html += '</dl>';
return html;
},
resolveConflict() {
if (!this.conflict || !this.formData || !this.modelClass) {
console.error('Missing required data for conflict resolution');
return;
}
// Create a form data object with the current form values
const formData = new FormData();
// Add all current form values
for (const [key, value] of Object.entries(this.formData)) {
formData.append(key, value);
}
// Add the model class
formData.append('model_class', this.modelClass);
// Send the request to restore the soft-deleted record
fetch(`/admin/soft-delete/resolve-conflict/${this.conflict.id}`, {
method: 'POST',
body: formData,
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Accept': 'application/json',
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Show success message
this.showNotification('Success', data.message, 'success');
// Close the modal
this.show = false;
// Redirect to the appropriate page (usually the index page)
const form = this.formElement;
const redirectUrl = form.dataset.successRedirect || form.dataset.cancelRedirect || '/';
window.location.href = redirectUrl;
} else {
// Show error message
this.showNotification('Error', data.message || 'Failed to restore record', 'error');
}
})
.catch(error => {
console.error('Error resolving conflict:', error);
this.showNotification('Error', 'An unexpected error occurred', 'error');
});
},
cancelOperation() {
this.show = false;
// If a cancel URL is specified on the form, redirect to it
const form = this.formElement;
if (form && form.dataset.cancelRedirect) {
window.location.href = form.dataset.cancelRedirect;
}
},
showNotification(title, message, type) {
// This can be customized to use your preferred notification system
// For now, we'll use a simple alert
alert(`${title}: ${message}`);
}
}));
});
</script>
<style>
[x-cloak] { display: none !important; }
</style>