added roles to member card
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Role } from "@app/shared/types/roles";
|
||||
import pool from "../db";
|
||||
import { Member, MemberLight, memberSettings, MemberState } from '@app/shared/types/member'
|
||||
import { Member, MemberCardDetails, MemberLight, memberSettings, MemberState } from '@app/shared/types/member'
|
||||
|
||||
export async function getUserData(userID: number): Promise<Member> {
|
||||
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id = ?`;
|
||||
@@ -60,10 +61,50 @@ export async function getAllMembersLite(): Promise<MemberLight[]> {
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function getMembersFull(ids: number[]): Promise<Member[]> {
|
||||
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id IN (?);`;
|
||||
const res: Member[] = await pool.query(sql, [ids]);
|
||||
return res;
|
||||
export async function getMembersFull(ids: number[]): Promise<MemberCardDetails[]> {
|
||||
const sql = `
|
||||
SELECT m.*,
|
||||
COALESCE(
|
||||
JSON_ARRAYAGG(
|
||||
CASE
|
||||
WHEN r.id IS NOT NULL THEN JSON_OBJECT(
|
||||
'id', r.id,
|
||||
'name', r.name,
|
||||
'color', r.color,
|
||||
'description', r.description
|
||||
)
|
||||
END
|
||||
),
|
||||
JSON_ARRAY()
|
||||
) AS roles
|
||||
FROM view_member_rank_unit_status_latest m
|
||||
LEFT JOIN members_roles mr ON m.member_id = mr.member_id
|
||||
LEFT JOIN roles r ON mr.role_id = r.id
|
||||
WHERE m.member_id IN (?)
|
||||
GROUP BY m.member_id;
|
||||
`;
|
||||
|
||||
const rows: any[] = await pool.query(sql, [ids]);
|
||||
|
||||
return rows.map(row => {
|
||||
const member: Member = {
|
||||
member_id: row.member_id,
|
||||
member_name: row.member_name,
|
||||
displayName: row.displayName,
|
||||
rank: row.rank,
|
||||
rank_date: row.rank_date,
|
||||
unit: row.unit,
|
||||
unit_date: row.unit_date,
|
||||
status: row.status,
|
||||
status_date: row.status_date,
|
||||
loa_until: row.loa_until ? new Date(row.loa_until) : undefined,
|
||||
};
|
||||
|
||||
// roles comes as array of strings; parse each one
|
||||
const roles: Role[] = JSON.parse(row.roles).map((r: string) => JSON.parse(r));
|
||||
|
||||
return { member, roles };
|
||||
});
|
||||
}
|
||||
|
||||
export async function mapDiscordtoID(id: number): Promise<number | null> {
|
||||
|
||||
@@ -34,6 +34,11 @@ export interface MemberLight {
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface MemberCardDetails {
|
||||
member: Member;
|
||||
roles: Role[];
|
||||
}
|
||||
|
||||
export interface myData {
|
||||
member: Member;
|
||||
LOAs: LOARequest[];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memberSettings, Member, MemberLight } from "@shared/types/member";
|
||||
import { memberSettings, Member, MemberLight, MemberCardDetails } from "@shared/types/member";
|
||||
|
||||
// @ts-ignore
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
@@ -71,7 +71,7 @@ export async function getLightMembers(ids: number[]): Promise<MemberLight[]> {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function getFullMembers(ids: number[]): Promise<Member[]> {
|
||||
export async function getFullMembers(ids: number[]): Promise<MemberCardDetails[]> {
|
||||
|
||||
if (ids.length === 0) return [];
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useMemberDirectory } from '@/stores/memberDirectory';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { Member, type MemberLight } from '@shared/types/member'
|
||||
import { Member, MemberCardDetails, type MemberLight } from '@shared/types/member'
|
||||
import Popover from '../ui/popover/Popover.vue';
|
||||
import PopoverTrigger from '../ui/popover/PopoverTrigger.vue';
|
||||
import PopoverContent from '../ui/popover/PopoverContent.vue';
|
||||
@@ -21,7 +21,7 @@ const props = defineProps({
|
||||
|
||||
// Local state
|
||||
const memberLight = ref<MemberLight | null>(null);
|
||||
const memberFull = ref<Member | null>(null)
|
||||
const memberFull = ref<MemberCardDetails | null>(null)
|
||||
const loadingFull = ref(false)
|
||||
const membersStore = useMemberDirectory();
|
||||
|
||||
@@ -63,7 +63,7 @@ const hasFullInfo = computed(() => {
|
||||
if (!memberFull.value) return false
|
||||
|
||||
// check if any field has a value
|
||||
const { rank, unit, status } = memberFull.value
|
||||
const { rank, unit, status } = memberFull.value.member
|
||||
return !!(rank || unit || status)
|
||||
})
|
||||
|
||||
@@ -90,7 +90,7 @@ function formatDate(date: Date): string {
|
||||
{{ displayName }}
|
||||
</p>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-72 p-0 overflow-hidden">
|
||||
<PopoverContent class="w-80 p-0 overflow-hidden">
|
||||
<!-- Loading -->
|
||||
<div v-if="loadingFull" class="p-4 text-sm text-muted-foreground mx-auto flex justify-center my-5">
|
||||
<Spinner></Spinner>
|
||||
@@ -114,26 +114,33 @@ function formatDate(date: Date): string {
|
||||
<div class="p-4 space-y-3 text-sm">
|
||||
<!-- Full info -->
|
||||
<template v-if="hasFullInfo">
|
||||
<div v-if="memberFull.loa_until"
|
||||
<div v-if="memberFull.member.loa_until"
|
||||
class=" rounded-md text-center bg-yellow-500/10 px-2 py-1 text-xs text-yellow-600">
|
||||
On Leave of Absence until {{ formatDate(memberFull.loa_until) }}
|
||||
On Leave of Absence until {{ formatDate(memberFull.member.loa_until) }}
|
||||
</div>
|
||||
|
||||
<div v-if="memberFull.rank" class="flex justify-between">
|
||||
|
||||
<div v-if="memberFull.member.rank" class="flex justify-between">
|
||||
<span class="text-muted-foreground">Rank</span>
|
||||
<span class="font-medium">{{ memberFull.rank }}</span>
|
||||
<span class="font-medium">{{ memberFull.member.rank }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="memberFull.unit" class="flex justify-between">
|
||||
<div v-if="memberFull.member.unit" class="flex justify-between">
|
||||
<span class="text-muted-foreground">Unit</span>
|
||||
<span class="font-medium">{{ memberFull.unit }}</span>
|
||||
<span class="font-medium">{{ memberFull.member.unit }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="memberFull.status" class="flex justify-between">
|
||||
<div v-if="memberFull.member.status" class="flex justify-between">
|
||||
<span class="text-muted-foreground">Status</span>
|
||||
<span class="font-medium">{{ memberFull.status }}</span>
|
||||
<span class="font-medium">{{ memberFull.member.status }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex gap-2 flex-wrap mt-6">
|
||||
<div v-for="role in memberFull.roles" class="border rounded-full px-3 text-nowrap">
|
||||
{{ role.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- No info fallback -->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineStore } from "pinia"
|
||||
import type { MemberLight, Member } from "@shared/types/member"
|
||||
import type { MemberLight, Member, MemberCardDetails } from "@shared/types/member"
|
||||
import { getLightMembers, getFullMembers } from "@/api/member"
|
||||
import { reactive, ref } from "vue"
|
||||
import { resolve } from "path"
|
||||
@@ -7,7 +7,7 @@ import { rejects } from "assert"
|
||||
|
||||
export const useMemberDirectory = defineStore('memberDirectory', () => {
|
||||
const light = reactive<Record<number, MemberLight>>({});
|
||||
const full = reactive<Record<number, Member>>({})
|
||||
const full = reactive<Record<number, MemberCardDetails>>({})
|
||||
|
||||
function getLight(id: number): Promise<MemberLight> {
|
||||
if (light[id]) return Promise.resolve(light[id]);
|
||||
@@ -24,7 +24,7 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
|
||||
})
|
||||
}
|
||||
|
||||
function getFull(id: number): Promise<Member> {
|
||||
function getFull(id: number): Promise<MemberCardDetails> {
|
||||
if (full[id]) return Promise.resolve(full[id])
|
||||
|
||||
if (!fullWaiters.has(id)) {
|
||||
@@ -34,7 +34,7 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
|
||||
|
||||
scheduleBatch()
|
||||
|
||||
return new Promise<Member>((resolve, reject) => {
|
||||
return new Promise<MemberCardDetails>((resolve, reject) => {
|
||||
fullWaiters.get(id)!.push({ resolve, reject })
|
||||
})
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
|
||||
|
||||
// promises
|
||||
const lightWaiters = new Map<number, Array<{ resolve: (m: MemberLight) => void; reject: (e: any) => void }>>()
|
||||
const fullWaiters = new Map<number, Array<{ resolve: (m: Member) => void; reject: (e: any) => void }>>()
|
||||
const fullWaiters = new Map<number, Array<{ resolve: (m: MemberCardDetails) => void; reject: (e: any) => void }>>()
|
||||
|
||||
let batchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
@@ -105,12 +105,13 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
|
||||
try {
|
||||
const res = await getFullMembers(ids);
|
||||
for (const m of res) {
|
||||
full[m.member_id] = m;
|
||||
console.log(m)
|
||||
full[m.member.member_id] = m;
|
||||
|
||||
const waiters = fullWaiters.get(m.member_id);
|
||||
const waiters = fullWaiters.get(m.member.member_id);
|
||||
if (waiters) {
|
||||
for (const w of waiters) w.resolve(m)
|
||||
fullWaiters.delete(m.member_id);
|
||||
fullWaiters.delete(m.member.member_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user