notifications fixed

This commit is contained in:
Simon Pocrnjič 2025-11-04 20:12:38 +01:00
parent 5f879c9436
commit ad8e0d5cee
5 changed files with 45 additions and 20 deletions

View File

@ -36,6 +36,6 @@ public function __invoke(Request $request)
] ]
); );
return response()->json(['status' => 'ok']); return back();
} }
} }

View File

@ -35,7 +35,15 @@ public function unread(Request $request)
->select(['id', 'due_date', 'amount', 'contract_id', 'client_case_id', 'created_at']) ->select(['id', 'due_date', 'amount', 'contract_id', 'client_case_id', 'created_at'])
->whereNotNull('due_date') ->whereNotNull('due_date')
->whereDate('due_date', '<=', $today) ->whereDate('due_date', '<=', $today)
// Removed per-user unread filter: show notifications regardless of individual reads // Exclude activities that have been marked as read by this user
->whereNotExists(function ($q) use ($user, $today) {
$q->select(\DB::raw(1))
->from('activity_notification_reads')
->whereColumn('activity_notification_reads.activity_id', 'activities.id')
->where('activity_notification_reads.user_id', $user->id)
->whereDate('activity_notification_reads.due_date', '<=', $today)
->whereNotNull('activity_notification_reads.read_at');
})
->when($clientCaseIdsForFilter->isNotEmpty(), function ($q) use ($clientCaseIdsForFilter) { ->when($clientCaseIdsForFilter->isNotEmpty(), function ($q) use ($clientCaseIdsForFilter) {
// Filter by clients: activities directly on any of the client's cases OR via contracts under those cases // Filter by clients: activities directly on any of the client's cases OR via contracts under those cases
$q->where(function ($qq) use ($clientCaseIdsForFilter) { $q->where(function ($qq) use ($clientCaseIdsForFilter) {
@ -108,7 +116,15 @@ public function unread(Request $request)
->select(['contract_id', 'client_case_id']) ->select(['contract_id', 'client_case_id'])
->whereNotNull('due_date') ->whereNotNull('due_date')
->whereDate('due_date', '<=', $today) ->whereDate('due_date', '<=', $today)
// Removed per-user unread filter for client list base // Exclude activities that have been marked as read by this user
->whereNotExists(function ($q) use ($user, $today) {
$q->select(\DB::raw(1))
->from('activity_notification_reads')
->whereColumn('activity_notification_reads.activity_id', 'activities.id')
->where('activity_notification_reads.user_id', $user->id)
->whereDate('activity_notification_reads.due_date', '<=', $today)
->whereNotNull('activity_notification_reads.read_at');
})
->when($clientCaseIdsForFilter->isNotEmpty(), function ($q) use ($clientCaseIdsForFilter) { ->when($clientCaseIdsForFilter->isNotEmpty(), function ($q) use ($clientCaseIdsForFilter) {
$q->where(function ($qq) use ($clientCaseIdsForFilter) { $q->where(function ($qq) use ($clientCaseIdsForFilter) {
$qq->whereIn('activities.client_case_id', $clientCaseIdsForFilter) $qq->whereIn('activities.client_case_id', $clientCaseIdsForFilter)

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { computed, onMounted, ref, watch } from "vue"; import { computed, onMounted, ref, watch } from "vue";
import { usePage, Link } from "@inertiajs/vue3"; import { usePage, Link, router } from "@inertiajs/vue3";
import Dropdown from "@/Components/Dropdown.vue"; import Dropdown from "@/Components/Dropdown.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { faBell } from "@fortawesome/free-solid-svg-icons"; import { faBell } from "@fortawesome/free-solid-svg-icons";
@ -53,7 +53,7 @@ watch(
} }
); );
async function markRead(item) { function markRead(item) {
const idx = items.value.findIndex((i) => i.id === item.id); const idx = items.value.findIndex((i) => i.id === item.id);
if (idx === -1) { if (idx === -1) {
return; return;
@ -62,14 +62,20 @@ async function markRead(item) {
// Optimistically remove // Optimistically remove
const removed = items.value.splice(idx, 1)[0]; const removed = items.value.splice(idx, 1)[0];
try { router.patch(
await window.axios.post(route("notifications.activity.read"), { route("notifications.activity.read"),
activity_id: item.id, { activity_id: item.id },
}); {
} catch (e) { onSuccess: () => {
// Rollback on failure // Item successfully marked as read
items.value.splice(idx, 0, removed); },
} onError: () => {
// Rollback on failure
items.value.splice(idx, 0, removed);
},
preserveScroll: true
}
);
} }
</script> </script>

View File

@ -67,11 +67,14 @@ watch(selectedClient, (val) => {
}); });
}); });
async function markRead(id) { function markRead(id) {
try { router.patch(route("notifications.activity.read"),
await window.axios.post(route("notifications.activity.read"), { activity_id: id }); { activity_id: id },
router.reload({ only: ["activities"] }); {
} catch (e) {} only: ["activities"],
preserveScroll: true
}
);
} }
</script> </script>

View File

@ -358,7 +358,7 @@
// Notifications: unread list and mark one activity as read (today) // Notifications: unread list and mark one activity as read (today)
Route::get('notifications/unread', [NotificationController::class, 'unread'])->name('notifications.unread'); Route::get('notifications/unread', [NotificationController::class, 'unread'])->name('notifications.unread');
Route::post('notifications/activity/read', ActivityNotificationController::class)->name('notifications.activity.read'); Route::patch('notifications/activity/read', ActivityNotificationController::class)->name('notifications.activity.read');
Route::delete('contracts/{contract:uuid}/documents/{document:uuid}', [ClientCaseContoller::class, 'deleteContractDocument'])->name('contract.document.delete'); Route::delete('contracts/{contract:uuid}/documents/{document:uuid}', [ClientCaseContoller::class, 'deleteContractDocument'])->name('contract.document.delete');
// settings // settings
Route::get('settings', [SettingController::class, 'index'])->name('settings'); Route::get('settings', [SettingController::class, 'index'])->name('settings');