Implement Site Translations API and DataGrid View: Introduced a new API controller for managing site translations, including methods for listing, storing, updating, and deleting translations. Added a DataGrid view for the site translations page, allowing for enhanced user interaction and management of translations. Implemented session-based view toggling between traditional table and DataGrid formats, improving usability. Localization support has been integrated for all text elements, ensuring a seamless experience for users across different languages.

This commit is contained in:
Ümit Tunç
2026-01-10 21:54:56 +03:00
parent 2fdf187658
commit e80c94146d
4 changed files with 730 additions and 1 deletions
@@ -0,0 +1,389 @@
<x-filament-panels::page>
@php
$languages = \App\Models\Language::where('is_active', true)->get();
$apiUrl = route('api.site-translations.index');
$apiStoreUrl = route('api.site-translations.store');
$apiUpdateUrl = route('api.site-translations.update', ['id' => '__ID__']);
$apiDeleteUrl = route('api.site-translations.destroy', ['id' => '__ID__']);
$apiBatchUpdateUrl = route('api.site-translations.batch-update');
$apiBatchDeleteUrl = route('api.site-translations.batch-destroy');
// CSRF token
$csrfToken = csrf_token();
@endphp
<div id="site-translations-datagrid" style="margin-top: 1rem;">
<div style="padding: 20px; text-align: center; color: #666;">
<p>DataGrid yükleniyor...</p>
</div>
</div>
<style>
#site-translations-datagrid {
width: 100%;
height: calc(100vh - 250px);
min-height: 600px;
}
.dx-datagrid-headers {
background-color: var(--fi-primary-50, #f0f9ff);
}
.dx-state-focused {
border-color: var(--fi-primary-600, #0284c7);
}
.dx-button.dx-button-success {
background-color: var(--fi-success-600, #16a34a);
border-color: var(--fi-success-600, #16a34a);
}
</style>
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.1.5/css/dx.light.css" />
<script src="https://cdn3.devexpress.com/jslib/23.1.5/js/dx.all.js"></script>
<script>
(function() {
function initializeDataGrid() {
// DOM element'inin hazır olduğunu kontrol et
const gridElement = document.getElementById('site-translations-datagrid');
if (!gridElement) {
console.error('DataGrid container bulunamadı!');
setTimeout(initializeDataGrid, 100);
return;
}
// DevExtreme'in yüklendiğini kontrol et
if (typeof DevExpress === 'undefined') {
console.error('DevExtreme yüklenemedi!');
gridElement.innerHTML =
'<div style="padding: 20px; color: red;">DevExtreme kütüphanesi yüklenemedi. Lütfen sayfayı yenileyin.</div>';
return;
}
console.log('DevExtreme yüklendi, DataGrid başlatılıyor...');
const languages = @json($languages->map(fn($lang) => ['code' => $lang->code, 'name' => $lang->name]));
const apiUrl = @json($apiUrl);
const apiStoreUrl = @json($apiStoreUrl);
const apiUpdateUrl = @json($apiUpdateUrl);
const apiDeleteUrl = @json($apiDeleteUrl);
const apiBatchUpdateUrl = @json($apiBatchUpdateUrl);
const apiBatchDeleteUrl = @json($apiBatchDeleteUrl);
const csrfToken = @json($csrfToken);
// Column definitions
const columns = [
{
dataField: 'key',
caption: 'Key / Orijinal Metin',
width: 300,
allowEditing: true,
editorType: 'dxTextArea',
editorOptions: {
height: 60
},
validationRules: [{ type: 'required', message: 'Key gereklidir' }]
}
];
// Add language columns
languages.forEach(lang => {
columns.push({
dataField: lang.code,
caption: `${lang.name} (${lang.code})`,
allowEditing: true,
width: 250,
editorType: 'dxTextArea',
editorOptions: {
height: 80,
maxHeight: 200
}
});
});
columns.push({
dataField: 'created_at',
caption: 'Oluşturulma',
dataType: 'datetime',
allowEditing: false,
width: 150,
visible: false
});
columns.push({
dataField: 'updated_at',
caption: 'Güncellenme',
dataType: 'datetime',
allowEditing: false,
width: 150,
visible: false
});
// Helper function for API calls
function apiCall(url, method, data) {
const options = {
method: method,
headers: {
'X-CSRF-TOKEN': csrfToken,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
if (data) {
options.body = JSON.stringify(data);
}
return fetch(url, options)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch(error => {
console.error('API Error:', error);
return { success: false, message: error.message };
});
}
// DataGrid configuration
const dataGrid = new DevExpress.data.DataSource({
load: function(loadOptions) {
const params = new URLSearchParams();
// Pagination
if (loadOptions.skip !== undefined && loadOptions.take) {
params.append('page', Math.floor(loadOptions.skip / loadOptions.take) + 1);
params.append('pageSize', loadOptions.take);
} else {
params.append('page', '1');
params.append('pageSize', '20');
}
// Sorting
if (loadOptions.sort) {
loadOptions.sort.forEach(sort => {
params.append('sort', sort.selector || sort);
params.append('order', sort.desc ? 'desc' : 'asc');
});
}
// Search
if (loadOptions.searchValue) {
params.append('search', loadOptions.searchValue);
}
const requestUrl = apiUrl + '?' + params.toString();
console.log('DataGrid loading data from:', requestUrl);
return apiCall(requestUrl, 'GET')
.then(function(response) {
console.log('DataGrid API response:', response);
if (!response || !response.data) {
console.error('Invalid API response:', response);
return {
data: [],
totalCount: 0
};
}
return {
data: response.data || [],
totalCount: response.totalCount || 0
};
})
.catch(function(error) {
console.error('DataGrid load error:', error);
DevExpress.ui.notify('Veri yüklenirken hata oluştu: ' + (error.message || 'Bilinmeyen hata'), 'error', 5000);
return {
data: [],
totalCount: 0
};
});
}
});
const dataGridInstance = new DevExpress.ui.dxDataGrid(gridElement, {
dataSource: dataGrid,
columns: columns,
editing: {
mode: 'batch',
allowAdding: true,
allowUpdating: true,
allowDeleting: true,
useIcons: true,
texts: {
addRow: 'Yeni Ekle',
saveAllChanges: 'Tüm Değişiklikleri Kaydet',
cancelAllChanges: 'İptal',
confirmDeleteMessage: 'Bu kaydı silmek istediğinizden emin misiniz?'
}
},
searchPanel: {
visible: true,
width: 300,
placeholder: 'Ara...'
},
headerFilter: {
visible: true
},
filterRow: {
visible: true
},
paging: {
pageSize: 20,
pageIndex: 0
},
pager: {
showPageSizeSelector: true,
allowedPageSizes: [10, 20, 50, 100],
showInfo: true,
showNavigationButtons: true
},
sorting: {
mode: 'multiple'
},
showBorders: true,
rowAlternationEnabled: true,
hoverStateEnabled: true,
columnAutoWidth: true,
allowColumnResizing: true,
columnResizingMode: 'widget',
wordWrapEnabled: true,
onSaving: function(e) {
e.cancel = true;
const changes = e.changes || [];
const insertPromises = [];
const updateChanges = [];
const deletePromises = [];
// Separate changes by type
changes.forEach(function(change) {
if (change.type === 'insert') {
const requestData = {
key: change.data.key || ''
};
languages.forEach(lang => {
if (change.data[lang.code] !== undefined) {
requestData[lang.code] = change.data[lang.code] || '';
}
});
insertPromises.push(apiCall(apiStoreUrl, 'POST', requestData));
} else if (change.type === 'update') {
const oldData = change.key;
const newData = change.data;
const requestData = {
key: newData.key !== undefined ? newData.key : oldData.key
};
languages.forEach(lang => {
if (newData[lang.code] !== undefined) {
requestData[lang.code] = newData[lang.code] || '';
} else if (oldData[lang.code] !== undefined) {
requestData[lang.code] = oldData[lang.code] || '';
}
});
updateChanges.push({
key: oldData.id,
data: requestData
});
} else if (change.type === 'remove') {
deletePromises.push(
apiCall(apiDeleteUrl.replace('__ID__', change.key.id), 'DELETE')
);
}
});
// Handle batch updates
let updatePromise = Promise.resolve({ success: true, updated: [] });
if (updateChanges.length > 0) {
updatePromise = apiCall(apiBatchUpdateUrl, 'POST', {
changes: updateChanges
});
}
// Execute all promises
Promise.all([
Promise.all(insertPromises),
updatePromise,
Promise.all(deletePromises)
]).then(function(results) {
const [insertResults, updateResult, deleteResults] = results;
let successCount = 0;
let errorCount = 0;
let errorMessages = [];
insertResults.forEach(function(result) {
if (result.success) {
successCount++;
} else {
errorCount++;
if (result.message) errorMessages.push(result.message);
}
});
if (updateResult.success) {
successCount += updateResult.updated ? updateResult.updated.length : 0;
if (updateResult.errors && updateResult.errors.length > 0) {
errorCount += updateResult.errors.length;
errorMessages = errorMessages.concat(updateResult.errors);
}
} else {
errorCount++;
if (updateResult.message) errorMessages.push(updateResult.message);
}
deleteResults.forEach(function(result) {
if (result.success) {
successCount++;
} else {
errorCount++;
if (result.message) errorMessages.push(result.message);
}
});
if (errorCount === 0) {
DevExpress.ui.notify(
`${successCount} işlem başarıyla tamamlandı`,
'success',
3000
);
e.component.refresh();
} else {
const message = errorMessages.length > 0
? errorMessages.join('; ')
: `${errorCount} işlem başarısız oldu`;
DevExpress.ui.notify(message, 'error', 5000);
e.component.refresh();
}
}).catch(function(error) {
DevExpress.ui.notify(
'İşlem sırasında hata oluştu: ' + (error.message || 'Bilinmeyen hata'),
'error',
5000
);
e.component.refresh();
});
}
});
console.log('DataGrid başlatıldı:', dataGridInstance);
}
// Sayfa yüklendiğinde başlat
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(initializeDataGrid, 500);
});
} else {
// DOM zaten hazır, DevExtreme yüklenene kadar bekle
setTimeout(initializeDataGrid, 500);
}
})();
</script>
</x-filament-panels::page>