diff --git a/src/modules/ZachetCard/ZachetCard.vue b/src/modules/ZachetCard/ZachetCard.vue new file mode 100644 index 0000000..3751e60 --- /dev/null +++ b/src/modules/ZachetCard/ZachetCard.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/src/modules/ZachetCard/controller/mapper.ts b/src/modules/ZachetCard/controller/mapper.ts new file mode 100644 index 0000000..845539c --- /dev/null +++ b/src/modules/ZachetCard/controller/mapper.ts @@ -0,0 +1,66 @@ +import type { UserdataItem, ZachetCardData } from './types'; + +const FALLBACK = '—'; + +function normalizeValue(value?: string | null): string | null { + if (typeof value !== 'string') return null; + + const trimmed = value.trim(); + + return trimmed.length ? trimmed : null; +} + +function getValue(items: UserdataItem[], category: string, param: string): string | null { + const item = items.find(entry => entry.category === category && entry.param === param); + + return normalizeValue(item?.value); +} + +function resolveUnionCardNumber(items: UserdataItem[]): string { + return ( + getValue(items, 'Учёба', 'Номер профсоюзного билета') ?? + getValue(items, 'Учетные данные', 'Номер профсоюзного билета') ?? + FALLBACK + ); +} + +function resolvePhotoUrl(items: UserdataItem[]): string | undefined { + const value = getValue(items, 'Личная информация', 'Фото'); + + if (!value) return undefined; + + return value; +} + +export function mapUserdataToZachetCard(items: UserdataItem[]): ZachetCardData { + logZachetCardMapper('start mapping items', { items }); + + const mappedCard: ZachetCardData = { + unionCardNumber: resolveUnionCardNumber(items), + fullNameRu: getValue(items, 'Личная информация', 'Полное имя') ?? FALLBACK, + fullNameEn: FALLBACK, + birthDate: getValue(items, 'Личная информация', 'Дата рождения') ?? FALLBACK, + facultyRu: getValue(items, 'Учёба', 'Факультет') ?? FALLBACK, + facultyEn: FALLBACK, + statusRu: getValue(items, 'Учёба', 'Ступень обучения') ?? FALLBACK, + statusEn: FALLBACK, + photoUrl: resolvePhotoUrl(items), + }; + + logZachetCardMapper('mapped card result', mappedCard); + + return mappedCard; +} + +function logZachetCardMapper(message: string, payload?: unknown) { + if (!import.meta.env.DEV) { + return; + } + + if (payload === undefined) { + console.log('[ZachetCard][mapper]', message); + return; + } + + console.log('[ZachetCard][mapper]', message, payload); +} diff --git a/src/modules/ZachetCard/controller/store.ts b/src/modules/ZachetCard/controller/store.ts new file mode 100644 index 0000000..2718489 --- /dev/null +++ b/src/modules/ZachetCard/controller/store.ts @@ -0,0 +1,91 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { UserdataApi } from '@/api/controllers/UserdataApi'; +import { mapUserdataToZachetCard } from './mapper'; +import type { ZachetCardData, UserdataResponse } from './types'; + +export const useZachetCardStore = defineStore('zachetCard', () => { + const cards = ref>({}); + const loadingByUserId = ref>({}); + const errorByUserId = ref>({}); + + async function fetchCard(userId: number, force = false): Promise { + logZachetCardStore('fetchCard called', { userId, force }); + + if (!force && cards.value[userId]) { + logZachetCardStore('return cached card', { + userId, + card: cards.value[userId], + }); + + return cards.value[userId]; + } + + loadingByUserId.value[userId] = true; + errorByUserId.value[userId] = null; + + try { + const response = await UserdataApi.getUser(userId); + const data = response?.data as UserdataResponse | undefined; + + logZachetCardStore('raw response data', data); + + const items = Array.isArray(data?.items) ? data.items : []; + const mappedCard = mapUserdataToZachetCard(items); + + cards.value[userId] = mappedCard; + + logZachetCardStore('card saved to store', { + userId, + card: mappedCard, + }); + + return mappedCard; + } catch (error) { + errorByUserId.value[userId] = 'Не удалось загрузить данные карты'; + errorZachetCardStore('fetchCard error', error); + + return null; + } finally { + loadingByUserId.value[userId] = false; + logZachetCardStore('fetchCard finished', { + userId, + loading: loadingByUserId.value[userId], + error: errorByUserId.value[userId], + }); + } + } + + return { + cards, + loadingByUserId, + errorByUserId, + fetchCard, + }; +}); + +function logZachetCardStore(message: string, payload?: unknown) { + if (!import.meta.env.DEV) { + return; + } + + if (payload === undefined) { + console.log('[ZachetCard][store]', message); + return; + } + + console.log('[ZachetCard][store]', message, payload); +} + +function errorZachetCardStore(message: string, payload?: unknown) { + if (!import.meta.env.DEV) { + return; + } + + if (payload === undefined) { + console.error('[ZachetCard][store]', message); + return; + } + + console.error('[ZachetCard][store]', message, payload); +} diff --git a/src/modules/ZachetCard/controller/types.ts b/src/modules/ZachetCard/controller/types.ts new file mode 100644 index 0000000..810a31e --- /dev/null +++ b/src/modules/ZachetCard/controller/types.ts @@ -0,0 +1,21 @@ +export interface UserdataItem { + category: string; + param: string; + value?: string | null; +} + +export interface UserdataResponse { + items: UserdataItem[]; +} + +export interface ZachetCardData { + unionCardNumber: string; + fullNameRu: string; + fullNameEn: string; + birthDate: string; + facultyRu: string; + facultyEn: string; + statusRu: string; + statusEn: string; + photoUrl?: string; +} diff --git a/src/modules/ZachetCard/controller/useZachetCardController.ts b/src/modules/ZachetCard/controller/useZachetCardController.ts new file mode 100644 index 0000000..c14cb0f --- /dev/null +++ b/src/modules/ZachetCard/controller/useZachetCardController.ts @@ -0,0 +1,102 @@ +import { computed, onMounted, watch } from 'vue'; +import { useProfileStore } from '@/store/profile'; +import { useZachetCardStore } from './store'; + +interface UseZachetCardControllerProps { + userId?: number; +} + +export function useZachetCardController(props: UseZachetCardControllerProps) { + const profileStore = useProfileStore(); + const zachetCardStore = useZachetCardStore(); + + const resolvedUserId = computed(() => props.userId ?? profileStore.id ?? null); + + const card = computed(() => { + const userId = resolvedUserId.value; + + if (!userId) return null; + + return zachetCardStore.cards[userId] ?? null; + }); + + const loading = computed(() => { + const userId = resolvedUserId.value; + + if (!userId) return false; + + return Boolean(zachetCardStore.loadingByUserId[userId]); + }); + + const error = computed(() => { + const userId = resolvedUserId.value; + + if (!userId) return 'Не найден id пользователя'; + + return zachetCardStore.errorByUserId[userId] ?? null; + }); + + async function load(force = false) { + const userId = resolvedUserId.value; + + logZachetCardController('load called', { + userId, + force, + }); + + if (!userId) { + logZachetCardController('load skipped because userId is empty'); + return; + } + + await zachetCardStore.fetchCard(userId, force); + } + + async function reload() { + logZachetCardController('reload called'); + await load(true); + } + + onMounted(() => { + logZachetCardController('controller mounted', { + resolvedUserId: resolvedUserId.value, + }); + + void load(); + }); + + watch( + resolvedUserId, + (nextUserId, prevUserId) => { + logZachetCardController('resolvedUserId changed', { + prevUserId, + nextUserId, + }); + + if (nextUserId && nextUserId !== prevUserId) { + void load(); + } + }, + { immediate: false } + ); + + return { + card, + loading, + error, + reload, + }; +} + +function logZachetCardController(message: string, payload?: unknown) { + if (!import.meta.env.DEV) { + return; + } + + if (payload === undefined) { + console.log('[ZachetCard][controller]', message); + return; + } + + console.log('[ZachetCard][controller]', message, payload); +} diff --git a/src/modules/ZachetCard/index.ts b/src/modules/ZachetCard/index.ts new file mode 100644 index 0000000..8a947c7 --- /dev/null +++ b/src/modules/ZachetCard/index.ts @@ -0,0 +1 @@ +export { default as ZachetCard } from './ZachetCard.vue'; diff --git a/src/router/index.ts b/src/router/index.ts index fbf1afc..d06c660 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -69,6 +69,17 @@ const routes: RouteRecordRaw[] = [ path: '/:pathMatch(.*)', component: () => import('@/views/error/Error404View.vue'), }, + { + path: '/debug/zachet-card', + component: () => import('@/views/debug/ZachetCardDebugView.vue'), + beforeEnter: () => { + const token = LocalStorage.get(LocalStorageItem.Token); + + if (!token) { + return { path: '/auth' }; + } + }, + }, ]; const router = createRouter({ diff --git a/src/views/debug/ZachetCardDebugView.vue b/src/views/debug/ZachetCardDebugView.vue new file mode 100644 index 0000000..0e2be5a --- /dev/null +++ b/src/views/debug/ZachetCardDebugView.vue @@ -0,0 +1,77 @@ + + + + +