ikinci temizlik tamamlandı
This commit is contained in:
@@ -0,0 +1,635 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
if(getisset("ajax")) {
|
||||
?>
|
||||
@include("admin-ajax.{$_GET['ajax']}")
|
||||
<?php
|
||||
exit();
|
||||
} ?>
|
||||
<?php if(getisset("ajax2")) { //blade ajax system
|
||||
?>
|
||||
@if(getisset("cache"))
|
||||
@includeCache("{$_GET['ajax2']}")
|
||||
@else
|
||||
@include("{$_GET['ajax2']}")
|
||||
@endif
|
||||
|
||||
<?php
|
||||
exit();
|
||||
} ?>
|
||||
@php
|
||||
$permissions = userPermissions();
|
||||
@endphp
|
||||
<?php $u = u(); ?>
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="{{App::getLocale()}}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>{!! strip_tags($__env->yieldContent('title','')) !!}</title>
|
||||
<link rel="manifest" href="/assets/manifest.json">
|
||||
<meta name="author" content="Truncgil Technology">
|
||||
<meta property="og:title" content="">
|
||||
<meta property="og:site_name" content="https://www.truncgil.com.tr/">
|
||||
<meta property="og:description" content="">
|
||||
<meta property="og:type" content="app">
|
||||
<meta property="og:url" content>
|
||||
<meta property="og:image" content>
|
||||
<div class="header-zone">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{url("assets/favicon.png")}}">
|
||||
<link rel="shortcut icon" href="{{url("assets/favicon.png")}}" type="image/png">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="theme-color" content="#fff"/>
|
||||
<meta name="description" content="{!! strip_tags($__env->yieldContent('title','')) !!}">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="/assets/favicon.png" type="image/png">
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/service-worker.js')
|
||||
.then(function(registration) {
|
||||
console.log('Service Worker registered with scope:', registration.scope);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log('Service Worker registration failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@include("admin.inc.plugins")
|
||||
|
||||
@yield('scripts')
|
||||
|
||||
</div>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@guest
|
||||
@yield("content")
|
||||
@else
|
||||
<div id="page-container"
|
||||
@if(isset($contents))
|
||||
class="sidebar-o enable-page-overlay side-scroll page-header-fixed page-header-glass side-trans-enabled sidebar-inverse"
|
||||
@endif
|
||||
>
|
||||
|
||||
|
||||
<aside id="side-overlay">
|
||||
<div class="content-header content-header-fullrow">
|
||||
<div class="content-header-section align-parent">
|
||||
<button type="button" class="btn btn-circle btn-dual-secondary align-v-r" data-toggle="layout" data-action="side_overlay_close">
|
||||
<i class="fa fa-times text-danger"></i>
|
||||
</button>
|
||||
<div class="content-header-item">
|
||||
<a class="align-middle text-primary-dark font-w600" href="#">
|
||||
{{ Auth::user()->name }} {{ Auth::user()->surname }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include("admin.inc.side-overlay")
|
||||
</aside>
|
||||
|
||||
@if(isset($contents))
|
||||
<nav id="sidebar">
|
||||
<div class="sidebar-content">
|
||||
|
||||
<div class="content-side content-side-full content-side-user px-10 align-parent">
|
||||
<!-- Visible only in mini mode -->
|
||||
<div class="sidebar-mini-visible-b align-v animated fadeIn">
|
||||
<a href="{{url("admin")}}">
|
||||
<img class="img-fluid" src="{{ url("assets/logo.svg") }}" alt="">
|
||||
</a>
|
||||
</div>
|
||||
<!-- END Visible only in mini mode -->
|
||||
|
||||
<!-- Visible only in normal mode -->
|
||||
<div class="sidebar-mini-hidden-b text-center">
|
||||
<a class="img-link" href="{{url("admin")}}">
|
||||
<img class="img-fluid" src="{{ url("assets/logo.svg") }}" alt="">
|
||||
</a>
|
||||
<a class="text-center text-dual-primary-dark font-size-xs font-w600"
|
||||
href="#">{{ Auth::user()->name }} {{ Auth::user()->surname }}</a>
|
||||
|
||||
<div class="text-center badge badge-success">{{ Auth::user()->level }}</div>
|
||||
<div class="text-center badge badge-info">{{ Auth::user()->subcontructer }}</div>
|
||||
|
||||
<ul class="list-inline mt-10">
|
||||
<li class="list-inline-item">
|
||||
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<!-- Layout API, functionality initialized in Template._uiApiLayout() -->
|
||||
<a class="text-dual-primary-dark" data-toggle="layout"
|
||||
data-action="sidebar_style_inverse_toggle" href="javascript:void(0)">
|
||||
<i class="si si-drop"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a class="text-dual-primary-dark" href="{{url("logout")}}">
|
||||
<i class="si si-logout"></i>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<!-- END Visible only in normal mode -->
|
||||
</div>
|
||||
|
||||
|
||||
@include("admin.inc.menu")
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
@include("admin.inc.header")
|
||||
@endif
|
||||
|
||||
<div class="main-container">
|
||||
<div class="">
|
||||
|
||||
@if (View::hasSection('title'))
|
||||
<div class="bg-image d-none" >
|
||||
<div class="bg-white-op-90">
|
||||
<div class="content content-full content-top">
|
||||
<h1 class="text-center">@yield("title")<br /> </h1>
|
||||
<div class="text-center d-none">@yield("desc")</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="content-ajax" style="margin-top:70px">
|
||||
@yield("content")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
@include('admin.inc.feedback-modal')
|
||||
<footer id="page-footer" class="t-center" style=" background: gray;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
font-size: 12px;">
|
||||
<div class="footer-status"></div>
|
||||
</footer>
|
||||
</div>
|
||||
@endguest
|
||||
|
||||
<!--
|
||||
<link rel="stylesheet" href="//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" />
|
||||
<script type="text/javascript" src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
|
||||
-->
|
||||
|
||||
@include("admin.inc.script")
|
||||
|
||||
|
||||
|
||||
<style type="text/css">
|
||||
.hidden {
|
||||
display:none !important;
|
||||
}
|
||||
.visible {
|
||||
display:block !important;
|
||||
}
|
||||
.hidden-upload {
|
||||
display:none;
|
||||
|
||||
}
|
||||
.table-responsive {
|
||||
/* background: url(back.png) white center center / contain no-repeat !important; */
|
||||
/* background-attachment: fixed !important; */
|
||||
background-size: 20% !important;
|
||||
background-position: center !important;
|
||||
}
|
||||
.dz-filename {
|
||||
white-space:normal !important;
|
||||
height: 74px;
|
||||
|
||||
}
|
||||
</style>
|
||||
<div class="modal fade" id="modal-popin" tabindex="-1" role="dialog" aria-labelledby="modal-popin" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-popin" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="block block-themed block-transparent mb-0">
|
||||
<div class="block-header bg-primary-dark">
|
||||
<h3 class="block-title"></h3>
|
||||
<div class="block-options">
|
||||
<button type="button" class="btn-block-option" data-dismiss="modal" aria-label="Close">
|
||||
<i class="si si-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-content">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-alt-secondary" data-dismiss="modal">{{__('Hayır')}}</button>
|
||||
<a href="#" class="btn btn-alt-success tamam" data-dismiss="modal">
|
||||
<i class="fa fa-check"></i> {{__('Evet')}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification System CSS -->
|
||||
<style>
|
||||
.notification-dropdown .notification-badge {
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.notification-menu {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.notification-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.notification-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid rgba(17, 25, 40, 0.08);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
background: #ffffff;
|
||||
transition: background 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.notification-item:hover {
|
||||
background: #e8f1ff;
|
||||
}
|
||||
|
||||
.notification-item.unread {
|
||||
background: #eef6ff;
|
||||
}
|
||||
|
||||
.notification-item.unread::after {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #3a7bd5;
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
box-shadow: 0 0 0 2px rgba(58, 123, 213, 0.15);
|
||||
}
|
||||
|
||||
.notification-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.notification-item .icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(58, 123, 213, 0.1);
|
||||
color: #3a7bd5;
|
||||
margin-right: 12px;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification-item .icon i {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.notification-item .content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.notification-item .content .title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #111928;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.notification-item .content .message {
|
||||
font-size: 13px;
|
||||
color: #4a5568;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notification-item .content .meta {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.notification-item .content .meta i {
|
||||
font-size: 11px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.notification-empty {
|
||||
padding: 48px 24px;
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.notification-empty i {
|
||||
font-size: 36px;
|
||||
margin-bottom: 12px;
|
||||
color: rgba(58, 123, 213, 0.55);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Notification System JavaScript -->
|
||||
<script>
|
||||
// Performance: Debounce function to limit API calls
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Performance: Cache for notification count to avoid repeated API calls
|
||||
let notificationCountCache = null;
|
||||
let notificationCountCacheTime = 0;
|
||||
const CACHE_DURATION = 30000; // 30 seconds
|
||||
|
||||
// Load notifications for dropdown
|
||||
function loadNotifications() {
|
||||
$.ajax({
|
||||
url: '/api/notifications/recent',
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
displayNotifications(response.notifications);
|
||||
updateNotificationCount(response.unread_count);
|
||||
},
|
||||
error: function() {
|
||||
$('#notification-list').html(`
|
||||
<li class="notification-placeholder">
|
||||
<i class="fa fa-exclamation-triangle text-danger"></i>
|
||||
<div class="font-weight-bold">{{e2("Unable to load notifications")}}</div>
|
||||
<div class="small">{{e2("Please try again shortly")}}</div>
|
||||
</li>
|
||||
`);
|
||||
$('#notification-unread-indicator').text('{{e2("Error")}}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getNotificationIcon(notificationCode, isRead) {
|
||||
if (!isRead) {
|
||||
return 'fa-bell';
|
||||
}
|
||||
|
||||
if (!notificationCode) return 'fa-bell';
|
||||
|
||||
const code = notificationCode.toLowerCase();
|
||||
if (code.includes('success') || code.includes('complete') || code.includes('created')) return 'fa-check-circle';
|
||||
if (code.includes('error') || code.includes('fail')) return 'fa-exclamation-circle';
|
||||
if (code.includes('warning') || code.includes('alert')) return 'fa-exclamation-triangle';
|
||||
if (code.includes('info') || code.includes('update')) return 'fa-info-circle';
|
||||
if (code.includes('test') || code.includes('inspection')) return 'fa-clipboard-check';
|
||||
if (code.includes('document') || code.includes('file')) return 'fa-file-alt';
|
||||
if (code.includes('user') || code.includes('account')) return 'fa-user';
|
||||
if (code.includes('system') || code.includes('maintenance')) return 'fa-cog';
|
||||
|
||||
return 'fa-bell';
|
||||
}
|
||||
|
||||
// Display notifications in dropdown
|
||||
function displayNotifications(notifications) {
|
||||
const listContainer = $('#notification-list');
|
||||
const defaultTitle = '{{e2("Notification")}}';
|
||||
const defaultMessage = '{{e2("No additional details")}}';
|
||||
|
||||
if (!notifications || notifications.length === 0) {
|
||||
listContainer.html(`
|
||||
<li class="notification-placeholder">
|
||||
<i class="fa fa-bell-slash"></i>
|
||||
<div class="font-weight-bold">{{e2("No notifications")}}</div>
|
||||
<div class="small">{{e2("You are all caught up")}}</div>
|
||||
</li>
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
notifications.forEach(function(notif) {
|
||||
const isUnread = !notif.is_read;
|
||||
const unreadClass = isUnread ? 'unread' : '';
|
||||
const timeAgo = formatTimeAgo(notif.created_at);
|
||||
const link = notif.link || '#';
|
||||
const safeTitle = notif.title ? escapeHtml(notif.title) : defaultTitle;
|
||||
const safeMessage = notif.message ? escapeHtml(notif.message) : defaultMessage;
|
||||
const iconClass = getNotificationIcon(notif.notification_code, !isUnread);
|
||||
|
||||
const truncatedMessage = safeMessage.length > 100
|
||||
? safeMessage.substring(0, 100) + '...'
|
||||
: safeMessage;
|
||||
|
||||
html += `
|
||||
<li class="notification-item ${unreadClass}"
|
||||
data-id="${notif.id}"
|
||||
onclick="handleNotificationClick(${notif.id}, '${escapeHtml(link)}')"
|
||||
title="${safeMessage}">
|
||||
<div class="icon">
|
||||
<i class="fa ${iconClass}"></i>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="title">${safeTitle}</div>
|
||||
<div class="message">${truncatedMessage}</div>
|
||||
<div class="meta">
|
||||
<i class="fa fa-clock"></i>
|
||||
${timeAgo}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
|
||||
listContainer.html(html);
|
||||
}
|
||||
|
||||
// Handle notification click
|
||||
function handleNotificationClick(notificationId, link) {
|
||||
// Mark as read and wait for response before navigation
|
||||
$.ajax({
|
||||
url: '/api/notifications/mark-read/' + notificationId,
|
||||
method: 'POST',
|
||||
data: { _token: '{{ csrf_token() }}' },
|
||||
async: false, // Wait for mark-read to complete
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
updateNotificationCount(response.unread_count);
|
||||
$('[data-id="' + notificationId + '"]').removeClass('unread');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Navigate to link if provided
|
||||
if (link && link !== '#') {
|
||||
window.location.href = link;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all notifications as read
|
||||
function markAllAsRead() {
|
||||
$.ajax({
|
||||
url: '/api/notifications/mark-all-read',
|
||||
method: 'POST',
|
||||
data: { _token: '{{ csrf_token() }}' },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
updateNotificationCount(0);
|
||||
$('.notification-item').removeClass('unread');
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: '{{e2("Success")}}',
|
||||
text: '{{e2("All notifications marked as read")}}',
|
||||
timer: 2000,
|
||||
showConfirmButton: false
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update notification count badge
|
||||
function updateNotificationCount(count) {
|
||||
notificationCountCache = count;
|
||||
notificationCountCacheTime = Date.now();
|
||||
|
||||
const badge = $('#notification-count');
|
||||
const indicator = $('#notification-unread-indicator');
|
||||
if (count > 0) {
|
||||
const displayCount = count > 99 ? '99+' : count;
|
||||
badge.text(displayCount).show();
|
||||
if (indicator.length) {
|
||||
indicator.text(`${displayCount} {{e2("new")}}`).removeClass('badge-secondary').addClass('badge-primary');
|
||||
}
|
||||
} else {
|
||||
badge.hide();
|
||||
if (indicator.length) {
|
||||
indicator.text('{{e2("All caught up")}}').removeClass('badge-primary').addClass('badge-success');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch notification count (with caching)
|
||||
function fetchNotificationCount() {
|
||||
// Use cache if valid
|
||||
if (notificationCountCache !== null &&
|
||||
(Date.now() - notificationCountCacheTime) < CACHE_DURATION) {
|
||||
updateNotificationCount(notificationCountCache);
|
||||
return;
|
||||
}
|
||||
|
||||
// Performance: Only fetch count, not full notifications
|
||||
$.ajax({
|
||||
url: '/api/notifications/unread-count',
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
updateNotificationCount(response.unread_count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Format time ago
|
||||
function formatTimeAgo(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const seconds = Math.floor((now - date) / 1000);
|
||||
|
||||
if (seconds < 60) return '{{e2("Just now")}}';
|
||||
if (seconds < 3600) return Math.floor(seconds / 60) + ' {{e2("min ago")}}';
|
||||
if (seconds < 86400) return Math.floor(seconds / 3600) + ' {{e2("h ago")}}';
|
||||
if (seconds < 604800) return Math.floor(seconds / 86400) + ' {{e2("d ago")}}';
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
// Escape HTML to prevent XSS
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
// Initialize notification system on page load
|
||||
$(document).ready(function() {
|
||||
// Fetch initial count
|
||||
fetchNotificationCount();
|
||||
|
||||
// Poll for new notifications every 15 seconds (for testing - can be increased to 60s in production)
|
||||
setInterval(fetchNotificationCount, 15000);
|
||||
|
||||
// Prevent dropdown from closing when clicking inside
|
||||
$('.notification-menu').on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="screenshotOverlay" style="
|
||||
display:none;
|
||||
position:fixed;
|
||||
inset:0;
|
||||
background:rgba(0,0,0,.7);
|
||||
z-index:9999;
|
||||
padding:20px;
|
||||
overflow:auto;">
|
||||
<div style="background:#fff; padding:10px; max-width:90%; margin:0 auto;">
|
||||
<h5 style="margin-bottom:10px">Crop your screenshot</h5>
|
||||
<div id="overlayPreview"></div>
|
||||
<button id="useCropped" class="btn btn-sm btn-primary mt-2">Use this & open feedback</button>
|
||||
<button id="cancelCrop" class="btn btn-sm btn-secondary mt-2">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<style type="text/css">
|
||||
.cke_button__easyimageupload {
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user