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 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> {
|
export async function getUserData(userID: number): Promise<Member> {
|
||||||
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id = ?`;
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMembersFull(ids: number[]): Promise<Member[]> {
|
export async function getMembersFull(ids: number[]): Promise<MemberCardDetails[]> {
|
||||||
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id IN (?);`;
|
const sql = `
|
||||||
const res: Member[] = await pool.query(sql, [ids]);
|
SELECT m.*,
|
||||||
return res;
|
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> {
|
export async function mapDiscordtoID(id: number): Promise<number | null> {
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export interface MemberLight {
|
|||||||
color: string
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MemberCardDetails {
|
||||||
|
member: Member;
|
||||||
|
roles: Role[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface myData {
|
export interface myData {
|
||||||
member: Member;
|
member: Member;
|
||||||
LOAs: LOARequest[];
|
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
|
// @ts-ignore
|
||||||
const addr = import.meta.env.VITE_APIHOST;
|
const addr = import.meta.env.VITE_APIHOST;
|
||||||
@@ -71,7 +71,7 @@ export async function getLightMembers(ids: number[]): Promise<MemberLight[]> {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFullMembers(ids: number[]): Promise<Member[]> {
|
export async function getFullMembers(ids: number[]): Promise<MemberCardDetails[]> {
|
||||||
|
|
||||||
if (ids.length === 0) return [];
|
if (ids.length === 0) return [];
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMemberDirectory } from '@/stores/memberDirectory';
|
import { useMemberDirectory } from '@/stores/memberDirectory';
|
||||||
import { ref, onMounted, computed } from 'vue';
|
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 Popover from '../ui/popover/Popover.vue';
|
||||||
import PopoverTrigger from '../ui/popover/PopoverTrigger.vue';
|
import PopoverTrigger from '../ui/popover/PopoverTrigger.vue';
|
||||||
import PopoverContent from '../ui/popover/PopoverContent.vue';
|
import PopoverContent from '../ui/popover/PopoverContent.vue';
|
||||||
@@ -21,7 +21,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
const memberLight = ref<MemberLight | null>(null);
|
const memberLight = ref<MemberLight | null>(null);
|
||||||
const memberFull = ref<Member | null>(null)
|
const memberFull = ref<MemberCardDetails | null>(null)
|
||||||
const loadingFull = ref(false)
|
const loadingFull = ref(false)
|
||||||
const membersStore = useMemberDirectory();
|
const membersStore = useMemberDirectory();
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ const hasFullInfo = computed(() => {
|
|||||||
if (!memberFull.value) return false
|
if (!memberFull.value) return false
|
||||||
|
|
||||||
// check if any field has a value
|
// check if any field has a value
|
||||||
const { rank, unit, status } = memberFull.value
|
const { rank, unit, status } = memberFull.value.member
|
||||||
return !!(rank || unit || status)
|
return !!(rank || unit || status)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ function formatDate(date: Date): string {
|
|||||||
{{ displayName }}
|
{{ displayName }}
|
||||||
</p>
|
</p>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="w-72 p-0 overflow-hidden">
|
<PopoverContent class="w-80 p-0 overflow-hidden">
|
||||||
<!-- Loading -->
|
<!-- Loading -->
|
||||||
<div v-if="loadingFull" class="p-4 text-sm text-muted-foreground mx-auto flex justify-center my-5">
|
<div v-if="loadingFull" class="p-4 text-sm text-muted-foreground mx-auto flex justify-center my-5">
|
||||||
<Spinner></Spinner>
|
<Spinner></Spinner>
|
||||||
@@ -114,26 +114,33 @@ function formatDate(date: Date): string {
|
|||||||
<div class="p-4 space-y-3 text-sm">
|
<div class="p-4 space-y-3 text-sm">
|
||||||
<!-- Full info -->
|
<!-- Full info -->
|
||||||
<template v-if="hasFullInfo">
|
<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">
|
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>
|
||||||
|
|
||||||
<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="text-muted-foreground">Rank</span>
|
||||||
<span class="font-medium">{{ memberFull.rank }}</span>
|
<span class="font-medium">{{ memberFull.member.rank }}</span>
|
||||||
</div>
|
</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="text-muted-foreground">Unit</span>
|
||||||
<span class="font-medium">{{ memberFull.unit }}</span>
|
<span class="font-medium">{{ memberFull.member.unit }}</span>
|
||||||
</div>
|
</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="text-muted-foreground">Status</span>
|
||||||
<span class="font-medium">{{ memberFull.status }}</span>
|
<span class="font-medium">{{ memberFull.member.status }}</span>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
|
|
||||||
<!-- No info fallback -->
|
<!-- No info fallback -->
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from "pinia"
|
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 { getLightMembers, getFullMembers } from "@/api/member"
|
||||||
import { reactive, ref } from "vue"
|
import { reactive, ref } from "vue"
|
||||||
import { resolve } from "path"
|
import { resolve } from "path"
|
||||||
@@ -7,7 +7,7 @@ import { rejects } from "assert"
|
|||||||
|
|
||||||
export const useMemberDirectory = defineStore('memberDirectory', () => {
|
export const useMemberDirectory = defineStore('memberDirectory', () => {
|
||||||
const light = reactive<Record<number, MemberLight>>({});
|
const light = reactive<Record<number, MemberLight>>({});
|
||||||
const full = reactive<Record<number, Member>>({})
|
const full = reactive<Record<number, MemberCardDetails>>({})
|
||||||
|
|
||||||
function getLight(id: number): Promise<MemberLight> {
|
function getLight(id: number): Promise<MemberLight> {
|
||||||
if (light[id]) return Promise.resolve(light[id]);
|
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 (full[id]) return Promise.resolve(full[id])
|
||||||
|
|
||||||
if (!fullWaiters.has(id)) {
|
if (!fullWaiters.has(id)) {
|
||||||
@@ -34,7 +34,7 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
|
|||||||
|
|
||||||
scheduleBatch()
|
scheduleBatch()
|
||||||
|
|
||||||
return new Promise<Member>((resolve, reject) => {
|
return new Promise<MemberCardDetails>((resolve, reject) => {
|
||||||
fullWaiters.get(id)!.push({ resolve, reject })
|
fullWaiters.get(id)!.push({ resolve, reject })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,7 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
|
|||||||
|
|
||||||
// promises
|
// promises
|
||||||
const lightWaiters = new Map<number, Array<{ resolve: (m: MemberLight) => void; reject: (e: any) => void }>>()
|
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;
|
let batchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
@@ -105,12 +105,13 @@ export const useMemberDirectory = defineStore('memberDirectory', () => {
|
|||||||
try {
|
try {
|
||||||
const res = await getFullMembers(ids);
|
const res = await getFullMembers(ids);
|
||||||
for (const m of res) {
|
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) {
|
if (waiters) {
|
||||||
for (const w of waiters) w.resolve(m)
|
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