From 46d48be0cd38cd4f389f2ba7779f13a417c6f7bc Mon Sep 17 00:00:00 2001 From: Ethan Ma Date: Wed, 30 Apr 2025 01:38:18 -0500 Subject: [PATCH] Registering a new sig ui implemented + integration with Ethan's backend functions --- src/api/functions/siglead.ts | 114 +++++++++++++ src/api/index.ts | 2 + src/api/routes/siglead.ts | 171 +++++++++++++++++++ src/common/config.ts | 4 + src/common/orgs.ts | 2 +- src/common/types/siglead.ts | 24 +++ src/common/utils.ts | 44 +++++ src/ui/Router.tsx | 5 + src/ui/pages/siglead/EditSigLeads.page.tsx | 125 ++++++++++++++ src/ui/pages/siglead/ManageSigLeads.page.tsx | 155 +++-------------- src/ui/pages/siglead/SigScreenComponents.tsx | 21 ++- src/ui/pages/siglead/ViewSigLead.page.tsx | 39 ++--- 12 files changed, 542 insertions(+), 164 deletions(-) create mode 100644 src/api/functions/siglead.ts create mode 100644 src/api/routes/siglead.ts create mode 100644 src/common/types/siglead.ts create mode 100644 src/ui/pages/siglead/EditSigLeads.page.tsx diff --git a/src/api/functions/siglead.ts b/src/api/functions/siglead.ts new file mode 100644 index 00000000..b6cbaa38 --- /dev/null +++ b/src/api/functions/siglead.ts @@ -0,0 +1,114 @@ +import { + DynamoDBClient, + QueryCommand, + ScanCommand, +} from "@aws-sdk/client-dynamodb"; +import { unmarshall } from "@aws-sdk/util-dynamodb"; +import { OrganizationList } from "common/orgs.js"; +import { + SigDetailRecord, + SigMemberCount, + SigMemberRecord, +} from "common/types/siglead.js"; +import { transformSigLeadToURI } from "common/utils.js"; +import { string } from "zod"; + +export async function fetchMemberRecords( + sigid: string, + tableName: string, + dynamoClient: DynamoDBClient, +) { + const fetchSigMemberRecords = new QueryCommand({ + TableName: tableName, + KeyConditionExpression: "#sigid = :accessVal", + ExpressionAttributeNames: { + "#sigid": "sigGroupId", + }, + ExpressionAttributeValues: { + ":accessVal": { S: sigid }, + }, + ScanIndexForward: false, + }); + + const result = await dynamoClient.send(fetchSigMemberRecords); + + // Process the results + return (result.Items || []).map((item) => { + const unmarshalledItem = unmarshall(item); + return unmarshalledItem as SigMemberRecord; + }); +} + +export async function fetchSigDetail( + sigid: string, + tableName: string, + dynamoClient: DynamoDBClient, +) { + const fetchSigDetail = new QueryCommand({ + TableName: tableName, + KeyConditionExpression: "#sigid = :accessVal", + ExpressionAttributeNames: { + "#sigid": "sigid", + }, + ExpressionAttributeValues: { + ":accessVal": { S: sigid }, + }, + ScanIndexForward: false, + }); + + const result = await dynamoClient.send(fetchSigDetail); + + // Process the results + return (result.Items || [{}]).map((item) => { + const unmarshalledItem = unmarshall(item); + + // Strip '#' from access field + delete unmarshalledItem.leadGroupId; + delete unmarshalledItem.memberGroupId; + + return unmarshalledItem as SigDetailRecord; + })[0]; +} + +// select count(sigid) +// from table +// groupby sigid +export async function fetchSigCounts( + sigMemberTableName: string, + dynamoClient: DynamoDBClient, +) { + const scan = new ScanCommand({ + TableName: sigMemberTableName, + ProjectionExpression: "sigGroupId", + }); + + const result = await dynamoClient.send(scan); + + const ids2Name: Record = {}; + OrganizationList.forEach((org) => { + const sigid = transformSigLeadToURI(org); + ids2Name[sigid] = org; + }); + + const counts: Record = {}; + (result.Items || []).forEach((item) => { + const sigGroupId = item.sigGroupId?.S; + if (sigGroupId) { + counts[sigGroupId] = (counts[sigGroupId] || 0) + 1; + } + }); + + const joined: Record = {}; + Object.keys(counts).forEach((sigid) => { + joined[sigid] = [ids2Name[sigid], counts[sigid]]; + }); + + const countsArray: SigMemberCount[] = Object.entries(joined).map( + ([sigid, [signame, count]]) => ({ + sigid, + signame, + count, + }), + ); + return countsArray; +} diff --git a/src/api/index.ts b/src/api/index.ts index c9eb2573..7a810d5c 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -21,6 +21,7 @@ import * as dotenv from "dotenv"; import iamRoutes from "./routes/iam.js"; import ticketsPlugin from "./routes/tickets.js"; import linkryRoutes from "./routes/linkry.js"; +import sigleadRoutes from "./routes/siglead.js"; import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts"; import NodeCache from "node-cache"; import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; @@ -262,6 +263,7 @@ async function init(prettyPrint: boolean = false) { api.register(iamRoutes, { prefix: "/iam" }); api.register(ticketsPlugin, { prefix: "/tickets" }); api.register(linkryRoutes, { prefix: "/linkry" }); + api.register(sigleadRoutes, { prefix: "/siglead" }); api.register(mobileWalletRoute, { prefix: "/mobileWallet" }); api.register(stripeRoutes, { prefix: "/stripe" }); api.register(roomRequestRoutes, { prefix: "/roomRequests" }); diff --git a/src/api/routes/siglead.ts b/src/api/routes/siglead.ts new file mode 100644 index 00000000..33c3e785 --- /dev/null +++ b/src/api/routes/siglead.ts @@ -0,0 +1,171 @@ +import { FastifyPluginAsync } from "fastify"; +import { z } from "zod"; +import { AppRoles } from "../../common/roles.js"; +import { + BaseError, + DatabaseDeleteError, + DatabaseFetchError, + DatabaseInsertError, + NotFoundError, + UnauthenticatedError, + UnauthorizedError, + ValidationError, +} from "../../common/errors/index.js"; +import { NoDataRequest } from "../types.js"; +import { + DynamoDBClient, + QueryCommand, + DeleteItemCommand, + ScanCommand, + TransactWriteItemsCommand, + AttributeValue, + TransactWriteItem, + GetItemCommand, + TransactionCanceledException, +} from "@aws-sdk/client-dynamodb"; +import { CloudFrontKeyValueStoreClient } from "@aws-sdk/client-cloudfront-keyvaluestore"; +import { + genericConfig, + EVENT_CACHED_DURATION, + LinkryGroupUUIDToGroupNameMap, +} from "../../common/config.js"; +import { marshall, unmarshall } from "@aws-sdk/util-dynamodb"; +import rateLimiter from "api/plugins/rateLimiter.js"; +import { + deleteKey, + getLinkryKvArn, + setKey, +} from "api/functions/cloudfrontKvStore.js"; +import { zodToJsonSchema } from "zod-to-json-schema"; +import { + SigDetailRecord, + SigleadGetRequest, + SigMemberCount, + SigMemberRecord, +} from "common/types/siglead.js"; +import { + fetchMemberRecords, + fetchSigCounts, + fetchSigDetail, +} from "api/functions/siglead.js"; +import { intersection } from "api/plugins/auth.js"; + +const sigleadRoutes: FastifyPluginAsync = async (fastify, _options) => { + const limitedRoutes: FastifyPluginAsync = async (fastify) => { + /*fastify.register(rateLimiter, { + limit: 30, + duration: 60, + rateLimitIdentifier: "linkry", + });*/ + + fastify.get( + "/sigmembers/:sigid", + { + onRequest: async (request, reply) => { + /*await fastify.authorize(request, reply, [ + AppRoles.LINKS_MANAGER, + AppRoles.LINKS_ADMIN, + ]);*/ + }, + }, + async (request, reply) => { + const { sigid } = request.params; + const tableName = genericConfig.SigleadDynamoSigMemberTableName; + + // First try-catch: Fetch owner records + let memberRecords: SigMemberRecord[]; + try { + memberRecords = await fetchMemberRecords( + sigid, + tableName, + fastify.dynamoClient, + ); + } catch (error) { + request.log.error( + `Failed to fetch member records: ${error instanceof Error ? error.toString() : "Unknown error"}`, + ); + throw new DatabaseFetchError({ + message: "Failed to fetch member records from Dynamo table.", + }); + } + + // Send the response + reply.code(200).send(memberRecords); + }, + ); + + fastify.get( + "/sigdetail/:sigid", + { + onRequest: async (request, reply) => { + /*await fastify.authorize(request, reply, [ + AppRoles.LINKS_MANAGER, + AppRoles.LINKS_ADMIN, + ]);*/ + }, + }, + async (request, reply) => { + const { sigid } = request.params; + const tableName = genericConfig.SigleadDynamoSigDetailTableName; + + // First try-catch: Fetch owner records + let sigDetail: SigDetailRecord; + try { + sigDetail = await fetchSigDetail( + sigid, + tableName, + fastify.dynamoClient, + ); + } catch (error) { + request.log.error( + `Failed to fetch sig detail record: ${error instanceof Error ? error.toString() : "Unknown error"}`, + ); + throw new DatabaseFetchError({ + message: "Failed to fetch sig detail record from Dynamo table.", + }); + } + + // Send the response + reply.code(200).send(sigDetail); + }, + ); + + // fetch sig count + fastify.get( + "/sigcount", + { + onRequest: async (request, reply) => { + /*await fastify.authorize(request, reply, [ + AppRoles.LINKS_MANAGER, + AppRoles.LINKS_ADMIN, + ]);*/ + }, + }, + async (request, reply) => { + // First try-catch: Fetch owner records + let sigMemCounts: SigMemberCount[]; + try { + sigMemCounts = await fetchSigCounts( + genericConfig.SigleadDynamoSigMemberTableName, + fastify.dynamoClient, + ); + } catch (error) { + request.log.error( + `Failed to fetch sig member counts record: ${error instanceof Error ? error.toString() : "Unknown error"}`, + ); + throw new DatabaseFetchError({ + message: + "Failed to fetch sig member counts record from Dynamo table.", + }); + } + + // Send the response + reply.code(200).send(sigMemCounts); + }, + ); + }; + + fastify.register(limitedRoutes); +}; + +export default sigleadRoutes; diff --git a/src/common/config.ts b/src/common/config.ts index b1cbc975..db5d6b4b 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -30,6 +30,8 @@ export type GenericConfigType = { EventsDynamoTableName: string; CacheDynamoTableName: string; LinkryDynamoTableName: string; + SigleadDynamoSigDetailTableName: string; + SigleadDynamoSigMemberTableName: string; StripeLinksDynamoTableName: string; ConfigSecretName: string; EntraSecretName: string; @@ -70,6 +72,8 @@ const genericConfig: GenericConfigType = { StripeLinksDynamoTableName: "infra-core-api-stripe-links", CacheDynamoTableName: "infra-core-api-cache", LinkryDynamoTableName: "infra-core-api-linkry", + SigleadDynamoSigDetailTableName: "infra-core-api-sig-details", + SigleadDynamoSigMemberTableName: "infra-core-api-sig-member-details", ConfigSecretName: "infra-core-api-config", EntraSecretName: "infra-core-api-entra", EntraReadOnlySecretName: "infra-core-api-ro-entra", diff --git a/src/common/orgs.ts b/src/common/orgs.ts index 61d570d2..ee84d00e 100644 --- a/src/common/orgs.ts +++ b/src/common/orgs.ts @@ -4,7 +4,7 @@ export const SIGList = [ "GameBuilders", "SIGAIDA", "SIGGRAPH", - "ICPC", + "SIGICPC", "SIGMobile", "SIGMusic", "GLUG", diff --git a/src/common/types/siglead.ts b/src/common/types/siglead.ts new file mode 100644 index 00000000..2822cd4e --- /dev/null +++ b/src/common/types/siglead.ts @@ -0,0 +1,24 @@ +export type SigDetailRecord = { + sigid: string; + signame: string; + description: string; + }; + + export type SigMemberRecord = { + sigGroupId: string; + email: string; + designation: string; + memberName: string; + }; + + export type SigleadGetRequest = { + Params: { sigid: string }; + Querystring: undefined; + Body: undefined; + }; + + export type SigMemberCount = { + sigid: string; + signame: string; + count: number; + }; \ No newline at end of file diff --git a/src/common/utils.ts b/src/common/utils.ts index 786c998f..8959eccd 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -12,3 +12,47 @@ export function transformCommaSeperatedName(name: string) { } return name; } + +const notUnreservedCharsRegex = /[^a-zA-Z0-9\-._~]/g; +const reservedCharsRegex = /[:\/?#\[\]@!$&'()*+,;=]/g; +/** + * Transforms an organization name (sig lead) into a URI-friendly format. + * The function performs the following transformations: + * - Removes characters that are reserved or not unreserved. + * - Adds spaces between camel case words. + * - Converts reserved characters to spaces. + * - Converts all characters to lowercase and replaces all types of whitespace with hyphens. + * - Replaces any sequence of repeated hyphens with a single hyphen. + * - Refer to RFC 3986 https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 + * + * @param {string} org - The organization (sig lead) name to be transformed. + * @returns {string} - The transformed organization name, ready for use as a URL. + */ +export function transformSigLeadToURI(org: string) { + org = org + // change not reserved chars to spaces + .trim() + .replace(notUnreservedCharsRegex, " ") + .trim() + .replace(/\s/g, "-") + + // remove all that is reserved or not unreserved + .replace(reservedCharsRegex, "") + + // convert SIG -> sig for camel case + .replace(/SIG/g, "sig") + + // add hyphen for camel case + .replace(/([a-z])([A-Z])/g, "$1-$2") + + // lower + .toLowerCase() + + // add spaces between chars and numbers (seq2seq -> seq-2-seq) + .replace(/(?<=[a-z])([0-9]+)(?=[a-z])/g, "-$1-") + + // remove duplicate hyphens + .replace(/-{2,}/g, "-"); + + return org === "-" ? "" : org; +} diff --git a/src/ui/Router.tsx b/src/ui/Router.tsx index 99f0af8b..67dadddc 100644 --- a/src/ui/Router.tsx +++ b/src/ui/Router.tsx @@ -21,6 +21,7 @@ import { ManageIamPage } from './pages/iam/ManageIam.page'; import { ManageProfilePage } from './pages/profile/ManageProfile.page'; import { ManageStripeLinksPage } from './pages/stripe/ViewLinks.page'; import { ManageSigLeadsPage } from './pages/siglead/ManageSigLeads.page'; +import { EditSigLeadsPage } from './pages/siglead/EditSigLeads.page'; import { ViewSigLeadPage } from './pages/siglead/ViewSigLead.page'; import { ManageRoomRequestsPage } from './pages/roomRequest/RoomRequestLanding.page'; import { ViewRoomRequest } from './pages/roomRequest/ViewRoomRequest.page'; @@ -190,6 +191,10 @@ const authenticatedRouter = createBrowserRouter([ path: '/siglead-management', element: , }, + { + path: '/siglead-management/edit', + element: , + }, { path: '/siglead-management/:sigId', element: , diff --git a/src/ui/pages/siglead/EditSigLeads.page.tsx b/src/ui/pages/siglead/EditSigLeads.page.tsx new file mode 100644 index 00000000..35646ccf --- /dev/null +++ b/src/ui/pages/siglead/EditSigLeads.page.tsx @@ -0,0 +1,125 @@ +import { + Title, + Box, + TextInput, + Textarea, + Switch, + Select, + Button, + Loader, + Container, + Group, +} from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { DateTimePicker } from '@mantine/dates'; +import { useForm, zodResolver } from '@mantine/form'; +import { notifications } from '@mantine/notifications'; +import dayjs from 'dayjs'; +import React, { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { z } from 'zod'; +import { AuthGuard } from '@ui/components/AuthGuard'; +import { getRunEnvironmentConfig } from '@ui/config'; +import { useApi } from '@ui/util/api'; +import { OrganizationList as orgList } from '@common/orgs'; +import { AppRoles } from '@common/roles'; +import { ScreenComponent } from './SigScreenComponents'; +import { transformSigLeadToURI } from '@common/utils'; + +const baseSigSchema = z.object({ + signame: z + .string() + .min(1, 'Title is required') + .regex(/^[a-zA-Z0-9]+$/, 'Sig name should only contain alphanumeric characters'), + description: z.string().min(1, 'Description is required'), +}); + +type SigPostRequest = z.infer; + +export const EditSigLeadsPage: React.FC = () => { + const [isSubmitting, setIsSubmitting] = useState(false); + const navigate = useNavigate(); + const api = useApi('core'); + + const form = useForm({ + validate: zodResolver(baseSigSchema), + initialValues: { + signame: '', + description: '', + }, + }); + + const checkSigId = async (signame: string) => { + try { + const sigid = transformSigLeadToURI(signame); + const result = await api.get(`/api/v1/siglead/sigdetail/${sigid}`); + return result.data; + } catch (error) { + console.error('Error validating if sigid already exists', error); + notifications.show({ + message: `Error validating if sigid already exists`, + }); + } + }; + + const handleSubmit = async (sigdetails: SigPostRequest) => { + try { + setIsSubmitting(true); + const found = await checkSigId(sigdetails.signame); + if (found) { + form.setErrors({ + signame: 'This signame is reserved already.', + }); + setIsSubmitting(false); + return; + } + notifications.show({ + message: `This will eventually make to a post request with signame: + ${sigdetails.signame} and description: ${sigdetails.description} + `, + }); + //Post... + navigate('/siglead-management'); + } catch (error) { + setIsSubmitting(false); + console.error('Error creating sig:', error); + notifications.show({ + message: 'Failed to create sig, please try again.', + }); + } + }; + + return ( + + + Registering a new Sig +
+ + + + + + + +
+
+ ); +}; diff --git a/src/ui/pages/siglead/ManageSigLeads.page.tsx b/src/ui/pages/siglead/ManageSigLeads.page.tsx index 4b5fda39..10f1c4b9 100644 --- a/src/ui/pages/siglead/ManageSigLeads.page.tsx +++ b/src/ui/pages/siglead/ManageSigLeads.page.tsx @@ -8,7 +8,9 @@ import { Button, Loader, Container, + Group, } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; import { DateTimePicker } from '@mantine/dates'; import { useForm, zodResolver } from '@mantine/form'; import { notifications } from '@mantine/notifications'; @@ -22,153 +24,46 @@ import { useApi } from '@ui/util/api'; import { OrganizationList as orgList } from '@common/orgs'; import { AppRoles } from '@common/roles'; import { ScreenComponent } from './SigScreenComponents'; - -export function capitalizeFirstLetter(string: string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -const repeatOptions = ['weekly', 'biweekly'] as const; - -const baseBodySchema = z.object({ - title: z.string().min(1, 'Title is required'), - description: z.string().min(1, 'Description is required'), - start: z.date(), - end: z.optional(z.date()), - location: z.string().min(1, 'Location is required'), - locationLink: z.optional(z.string().url('Invalid URL')), - host: z.string().min(1, 'Host is required'), - featured: z.boolean().default(false), - paidEventId: z.string().min(1, 'Paid Event ID must be at least 1 character').optional(), -}); - -const requestBodySchema = baseBodySchema - .extend({ - repeats: z.optional(z.enum(repeatOptions)).nullable(), - repeatEnds: z.date().optional(), - }) - .refine((data) => (data.repeatEnds ? data.repeats !== undefined : true), { - message: 'Repeat frequency is required when Repeat End is specified.', - }) - .refine((data) => !data.end || data.end >= data.start, { - message: 'Event end date cannot be earlier than the start date.', - path: ['end'], - }) - .refine((data) => !data.repeatEnds || data.repeatEnds >= data.start, { - message: 'Repeat end date cannot be earlier than the start date.', - path: ['repeatEnds'], - }); - -type EventPostRequest = z.infer; +import { transformSigLeadToURI } from '@common/utils'; +import { + SigDetailRecord, + SigleadGetRequest, + SigMemberCount, + SigMemberRecord, +} from '@common/types/siglead'; export const ManageSigLeadsPage: React.FC = () => { const [isSubmitting, setIsSubmitting] = useState(false); + const [SigMemberCounts, setSigMemberCounts] = useState([]); const navigate = useNavigate(); const api = useApi('core'); - const { eventId } = useParams(); - - const isEditing = eventId !== undefined; - useEffect(() => { - if (!isEditing) { - return; - } - // Fetch event data and populate form - const getEvent = async () => { + const getMemberCounts = async () => { try { - const response = await api.get(`/api/v1/events/${eventId}`); - const eventData = response.data; - const formValues = { - title: eventData.title, - description: eventData.description, - start: new Date(eventData.start), - end: eventData.end ? new Date(eventData.end) : undefined, - location: eventData.location, - locationLink: eventData.locationLink, - host: eventData.host, - featured: eventData.featured, - repeats: eventData.repeats, - repeatEnds: eventData.repeatEnds ? new Date(eventData.repeatEnds) : undefined, - paidEventId: eventData.paidEventId, - }; - form.setValues(formValues); + const sigMemberCountsRequest = await api.get(`/api/v1/siglead/sigcount`); + setSigMemberCounts(sigMemberCountsRequest.data); } catch (error) { - console.error('Error fetching event data:', error); + console.error('Error fetching sig member counts:', error); notifications.show({ - message: 'Failed to fetch event data, please try again.', + message: 'Failed to fetch sig member counts, please try again.', }); } }; - getEvent(); - }, [eventId, isEditing]); - - const form = useForm({ - validate: zodResolver(requestBodySchema), - initialValues: { - title: '', - description: '', - start: new Date(), - end: new Date(new Date().valueOf() + 3.6e6), // 1 hr later - location: 'ACM Room (Siebel CS 1104)', - locationLink: 'https://maps.app.goo.gl/dwbBBBkfjkgj8gvA8', - host: 'ACM', - featured: false, - repeats: undefined, - repeatEnds: undefined, - paidEventId: undefined, - }, - }); - - const checkPaidEventId = async (paidEventId: string) => { - try { - const merchEndpoint = getRunEnvironmentConfig().ServiceConfiguration.merch.baseEndpoint; - const ticketEndpoint = getRunEnvironmentConfig().ServiceConfiguration.tickets.baseEndpoint; - const paidEventHref = paidEventId.startsWith('merch:') - ? `${merchEndpoint}/api/v1/merch/details?itemid=${paidEventId.slice(6)}` - : `${ticketEndpoint}/api/v1/event/details?eventid=${paidEventId}`; - const response = await api.get(paidEventHref); - return Boolean(response.status < 299 && response.status >= 200); - } catch (error) { - console.error('Error validating paid event ID:', error); - return false; - } - }; - - const handleSubmit = async (values: EventPostRequest) => { - try { - setIsSubmitting(true); - const realValues = { - ...values, - start: dayjs(values.start).format('YYYY-MM-DD[T]HH:mm:00'), - end: values.end ? dayjs(values.end).format('YYYY-MM-DD[T]HH:mm:00') : undefined, - repeatEnds: - values.repeatEnds && values.repeats - ? dayjs(values.repeatEnds).format('YYYY-MM-DD[T]HH:mm:00') - : undefined, - repeats: values.repeats ? values.repeats : undefined, - }; - - const eventURL = isEditing ? `/api/v1/events/${eventId}` : '/api/v1/events'; - const response = await api.post(eventURL, realValues); - notifications.show({ - title: isEditing ? 'Event updated!' : 'Event created!', - message: isEditing ? undefined : `The event ID is "${response.data.id}".`, - }); - navigate('/events/manage'); - } catch (error) { - setIsSubmitting(false); - console.error('Error creating/editing event:', error); - notifications.show({ - message: 'Failed to create/edit event, please try again.', - }); - } - }; + getMemberCounts(); + }, []); return ( - SigLead Management System - + + SigLead Management System + + + + {/* */} diff --git a/src/ui/pages/siglead/SigScreenComponents.tsx b/src/ui/pages/siglead/SigScreenComponents.tsx index f34cf995..af8ad09a 100644 --- a/src/ui/pages/siglead/SigScreenComponents.tsx +++ b/src/ui/pages/siglead/SigScreenComponents.tsx @@ -3,15 +3,19 @@ import { OrganizationList } from '@common/orgs'; import { NavLink, Paper } from '@mantine/core'; import { IconUsersGroup } from '@tabler/icons-react'; import { useLocation } from 'react-router-dom'; +import { SigMemberCount } from '@common/types/siglead'; -const renderSigLink = (org: string, index: number) => { +const renderSigLink = (sigMemCount: SigMemberCount, index: number) => { const color = 'light-dark(var(--mantine-color-black), var(--mantine-color-white))'; const size = '18px'; + const name = sigMemCount.signame; + const id = sigMemCount.sigid; + const count = sigMemCount.count; return ( { fontSize: `${size}`, }} > - MemberCount[{index}] + MemberCount: {count} } @@ -38,7 +42,11 @@ const renderSigLink = (org: string, index: number) => { ); }; -export const ScreenComponent: React.FC = () => { +type props = { + SigMemberCounts: SigMemberCount[]; +}; + +export const ScreenComponent: React.FC = ({ SigMemberCounts }) => { return ( <> { Organization Member Count - {OrganizationList.map(renderSigLink)} + {/* {OrganizationList.map(renderSigLink)} */} + {SigMemberCounts.map(renderSigLink)} ); }; diff --git a/src/ui/pages/siglead/ViewSigLead.page.tsx b/src/ui/pages/siglead/ViewSigLead.page.tsx index d0902e83..4bd5afc3 100644 --- a/src/ui/pages/siglead/ViewSigLead.page.tsx +++ b/src/ui/pages/siglead/ViewSigLead.page.tsx @@ -25,6 +25,7 @@ import { AuthGuard } from '@ui/components/AuthGuard'; import { getRunEnvironmentConfig } from '@ui/config'; import { useApi } from '@ui/util/api'; import { AppRoles } from '@common/roles'; +import { IconUsersGroup } from '@tabler/icons-react'; const baseSigSchema = z.object({ sigid: z.string().min(1), @@ -49,34 +50,17 @@ export const ViewSigLeadPage: React.FC = () => { const api = useApi('core'); const { colorScheme } = useMantineColorScheme(); const { sigId } = useParams(); - const [sigMembers, setSigMembers] = useState([ - { - sigGroupId: sigId || '', - email: 'alice1@illinois.edu', - designation: 'L', - memberName: 'Alice', - }, - { - sigGroupId: sigId || '', - email: 'bob2@illinois.edu', - designation: 'M', - memberName: 'Bob', - }, - ]); - const [sigDetails, setSigDetails] = useState({ - sigid: sigId || '', - signame: 'Default Sig', - description: - 'A cool Sig with a lot of money and members. Founded in 1999 by Sir Charlie of Edinburgh. Focuses on making money and helping others earn more money via education.', - }); + const [sigMembers, setSigMembers] = useState([]); + const [sigDetails, setSigDetails] = useState(); useEffect(() => { // Fetch sig data and populate form / for now dummy data... const getSig = async () => { try { - /*const formValues = { - }; - form.setValues(formValues);*/ + const sigDetailsData = await api.get(`/api/v1/siglead/sigdetail/${sigId}`); + setSigDetails(sigDetailsData.data); + const sigMembersData = await api.get(`/api/v1/siglead/sigmembers/${sigId}`); + setSigMembers(sigMembersData.data); } catch (error) { console.error('Error fetching sig data:', error); notifications.show({ @@ -168,13 +152,14 @@ export const ViewSigLeadPage: React.FC = () => { - {sigDetails.sigid} - {sigDetails.description || ''} + {sigDetails?.signame} + {sigDetails?.description || ''} - - +