|
|
@@ -20,71 +20,16 @@
|
|
|
<q-spinner color="violet-normal" size="50px" />
|
|
|
</div>
|
|
|
|
|
|
- <div v-else class="row q-col-gutter-md q-pt-xs">
|
|
|
- <div
|
|
|
+ <div v-else class="notifications-grid q-pt-xs">
|
|
|
+ <NotificationCard
|
|
|
v-for="item in notifications"
|
|
|
:key="item.id"
|
|
|
- class="col-xl-4 col-lg-4 col-md-6 col-sm-6 col-12"
|
|
|
- >
|
|
|
- <q-card
|
|
|
- flat
|
|
|
- bordered
|
|
|
- class="notif-card"
|
|
|
- :class="{ 'notif-card--unread': !item.read }"
|
|
|
- @click="openDetail(item)"
|
|
|
- >
|
|
|
- <div class="notif-card__image">
|
|
|
- <img
|
|
|
- v-if="imageUrl(item) && !imageErrors.has(item.id)"
|
|
|
- :src="imageUrl(item)"
|
|
|
- alt=""
|
|
|
- class="notif-card__img"
|
|
|
- @error="onImageError(item)"
|
|
|
- />
|
|
|
- <div v-else class="notif-card__placeholder flex flex-center">
|
|
|
- <q-icon
|
|
|
- name="mdi-bell-outline"
|
|
|
- size="40px"
|
|
|
- :color="item.read ? 'grey-4' : 'violet-normal'"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <q-card-section class="q-pt-sm q-pb-xs">
|
|
|
- <div class="row items-start justify-between no-wrap q-mb-xs">
|
|
|
- <div
|
|
|
- class="notif-card__title text-weight-bold ellipsis"
|
|
|
- :class="item.read ? 'text-grey-7' : 'text-violet-normal'"
|
|
|
- >
|
|
|
- {{ item.notification?.title }}
|
|
|
- </div>
|
|
|
- <q-badge
|
|
|
- v-if="!item.read"
|
|
|
- color="violet-normal"
|
|
|
- rounded
|
|
|
- class="q-ml-xs"
|
|
|
- style="flex-shrink: 0"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="notif-card__message text-caption text-grey-7">
|
|
|
- {{ item.notification?.message }}
|
|
|
- </div>
|
|
|
- </q-card-section>
|
|
|
-
|
|
|
- <q-card-actions class="q-pt-xs q-pb-sm q-px-md">
|
|
|
- <div class="text-caption text-grey-6">
|
|
|
- {{ formatDate(item.created_at) }}
|
|
|
- </div>
|
|
|
- <q-space />
|
|
|
- <q-icon
|
|
|
- v-if="item.read"
|
|
|
- name="mdi-check-circle"
|
|
|
- color="positive"
|
|
|
- size="18px"
|
|
|
- />
|
|
|
- </q-card-actions>
|
|
|
- </q-card>
|
|
|
- </div>
|
|
|
+ :notification="item.notification"
|
|
|
+ :unread="!item.read"
|
|
|
+ :date="item.created_at"
|
|
|
+ show-read-icon
|
|
|
+ @click="openDetail(item)"
|
|
|
+ />
|
|
|
</div>
|
|
|
</q-card-section>
|
|
|
|
|
|
@@ -108,7 +53,6 @@
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, computed, watch } from "vue";
|
|
|
-import { useI18n } from "vue-i18n";
|
|
|
import { useQuasar } from "quasar";
|
|
|
import { userStore } from "src/stores/user";
|
|
|
import {
|
|
|
@@ -116,6 +60,7 @@ import {
|
|
|
getMyUnreadNotificationsParceiro,
|
|
|
} from "src/api/notification";
|
|
|
import NotificationDetailDialog from "src/components/NotificationDetailDialog.vue";
|
|
|
+import NotificationCard from "src/components/NotificationCard.vue";
|
|
|
|
|
|
const props = defineProps({
|
|
|
modelValue: { type: Boolean, required: true },
|
|
|
@@ -123,13 +68,11 @@ const props = defineProps({
|
|
|
|
|
|
const emit = defineEmits(["update:modelValue"]);
|
|
|
|
|
|
-const { t } = useI18n();
|
|
|
const $q = useQuasar();
|
|
|
const store = userStore();
|
|
|
|
|
|
const loading = ref(false);
|
|
|
const notifications = ref([]);
|
|
|
-const imageErrors = ref(new Set());
|
|
|
|
|
|
const localUnreadCount = computed(() => notifications.value.filter((n) => !n.read).length);
|
|
|
|
|
|
@@ -148,30 +91,6 @@ const fetchUnread = async () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-const imageUrl = (item) => {
|
|
|
- const direct = item.notification?.image_url;
|
|
|
- if (!direct) return null;
|
|
|
- if (direct.startsWith("http")) return direct;
|
|
|
- const base = (process.env.API_URL ?? "").replace(/\/$/, "");
|
|
|
- return `${base}/${direct.replace(/^\//, "")}`;
|
|
|
-};
|
|
|
-
|
|
|
-const onImageError = (item) => {
|
|
|
- imageErrors.value = new Set(imageErrors.value).add(item.id);
|
|
|
-};
|
|
|
-
|
|
|
-const formatDate = (dateStr) => {
|
|
|
- if (!dateStr) return "";
|
|
|
- const date = new Date(dateStr);
|
|
|
- const today = new Date();
|
|
|
- const isToday =
|
|
|
- date.getDate() === today.getDate() &&
|
|
|
- date.getMonth() === today.getMonth() &&
|
|
|
- date.getFullYear() === today.getFullYear();
|
|
|
- if (isToday) return t("common.terms.today");
|
|
|
- return date.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
|
|
|
-};
|
|
|
-
|
|
|
const openDetail = (item) => {
|
|
|
$q.dialog({
|
|
|
component: NotificationDetailDialog,
|
|
|
@@ -208,54 +127,4 @@ watch(
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-.notif-card {
|
|
|
- border-radius: 8px;
|
|
|
- overflow: hidden;
|
|
|
- cursor: pointer;
|
|
|
- transition: box-shadow 0.2s, transform 0.15s;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- box-shadow: 0 4px 16px rgba(102, 29, 117, 0.15);
|
|
|
- transform: translateY(-2px);
|
|
|
- }
|
|
|
-
|
|
|
- &--unread {
|
|
|
- border-top: 3px solid #7b2d97;
|
|
|
- }
|
|
|
-
|
|
|
- &__image {
|
|
|
- width: 100%;
|
|
|
- height: 120px;
|
|
|
- overflow: hidden;
|
|
|
- }
|
|
|
-
|
|
|
- &__img {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- object-fit: cover;
|
|
|
- display: block;
|
|
|
- }
|
|
|
-
|
|
|
- &__placeholder {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background: #f0e8f1;
|
|
|
- }
|
|
|
-
|
|
|
- &__title {
|
|
|
- font-size: 14px;
|
|
|
- line-height: 1.3;
|
|
|
- min-width: 0;
|
|
|
- }
|
|
|
-
|
|
|
- &__message {
|
|
|
- display: -webkit-box;
|
|
|
- -webkit-line-clamp: 2;
|
|
|
- line-clamp: 2;
|
|
|
- -webkit-box-orient: vertical;
|
|
|
- overflow: hidden;
|
|
|
- line-height: 1.4;
|
|
|
- }
|
|
|
-}
|
|
|
</style>
|