214 lines
9.9 KiB
PHP
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>
|