From 7c1013d28a7a6f522e2938a416531a27498f7495 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Thu, 23 Oct 2025 23:14:02 +0800 Subject: [PATCH 1/4] refactor(points): move query to components --- .../points/_components/data-table.tsx | 85 +++++++++++++++---- .../points/_components/query.ts | 33 ------- gql/gql.ts | 12 ++- gql/graphql.ts | 28 +++++- schema.graphql | 13 +++ 5 files changed, 115 insertions(+), 56 deletions(-) delete mode 100644 app/(admin)/(activity-management)/points/_components/query.ts diff --git a/app/(admin)/(activity-management)/points/_components/data-table.tsx b/app/(admin)/(activity-management)/points/_components/data-table.tsx index a034825..3044fd6 100644 --- a/app/(admin)/(activity-management)/points/_components/data-table.tsx +++ b/app/(admin)/(activity-management)/points/_components/data-table.tsx @@ -6,7 +6,51 @@ import { useSuspenseQuery } from "@apollo/client/react"; import type { VariablesOf } from "@graphql-typed-document-node/core"; import { useState } from "react"; import { columns, type Point } from "./data-table-columns"; -import { POINTS_TABLE_QUERY } from "./query"; +import { graphql, useFragment as readFragment } from "@/gql"; + +const POINTS_TABLE_QUERY = graphql(` + query PointsTable( + $first: Int + $after: Cursor + $last: Int + $before: Cursor + $where: PointWhereInput + ) { + points( + first: $first + after: $after + last: $last + before: $before + where: $where + orderBy: { field: GRANTED_AT, direction: DESC } + ) { + edges { + node { + id + ...PointsTableRow + } + } + totalCount + pageInfo { + hasNextPage + endCursor + } + } + } +`); + +const POINTS_TABLE_ROW_FRAGEMENT = graphql(` + fragment PointsTableRow on Point { + id + user { + id + name + } + points + description + grantedAt + } +`); export function PointsDataTable({ query }: { query?: string }) { const PAGE_SIZE = 20; @@ -25,22 +69,27 @@ export function PointsDataTable({ query }: { query?: string }) { variables, }); - const pointsList = data?.points.edges - ?.map((edge) => { - const point = edge?.node; - if (!point) return null; - return { - id: point.id, - user: { - id: point.user.id, - name: point.user.name, - }, - points: point.points, - description: point.description ?? "", - grantedAt: point.grantedAt, - } satisfies Point; - }) - .filter((point) => point !== null) ?? []; + const pointsList = + data?.points.edges + ?.map((edge) => { + const node = edge?.node; + if (!node) return null; + + const point = readFragment(POINTS_TABLE_ROW_FRAGEMENT, node); + + if (!point) return null; + return { + id: point.id, + user: { + id: point.user.id, + name: point.user.name, + }, + points: point.points, + description: point.description ?? "", + grantedAt: point.grantedAt, + } satisfies Point; + }) + .filter((point) => point !== null) ?? []; const pageInfo = data?.points.pageInfo; @@ -48,7 +97,7 @@ export function PointsDataTable({ query }: { query?: string }) { if (!pageInfo) return; if (direction === "forward" && pageInfo.hasNextPage) { const nextCursor = pageInfo.endCursor ?? null; - setCursors(prev => { + setCursors((prev) => { const newCursors = prev.slice(0, currentIndex + 1); newCursors.push(nextCursor); return newCursors; diff --git a/app/(admin)/(activity-management)/points/_components/query.ts b/app/(admin)/(activity-management)/points/_components/query.ts deleted file mode 100644 index f56dee5..0000000 --- a/app/(admin)/(activity-management)/points/_components/query.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { graphql } from "@/gql"; - -export const POINTS_TABLE_QUERY = graphql(` - query PointsTable( - $first: Int - $after: Cursor - $last: Int - $before: Cursor - $where: PointWhereInput - ) { - points(first: $first, after: $after, last: $last, before: $before, where: $where, orderBy: { field: GRANTED_AT, direction: DESC }) { - edges { - node { - id - user { - id - name - } - points - description - grantedAt - } - } - totalCount - pageInfo { - hasNextPage - hasPreviousPage - endCursor - startCursor - } - } - } -`); diff --git a/gql/gql.ts b/gql/gql.ts index ab2ecab..3a3c5f3 100644 --- a/gql/gql.ts +++ b/gql/gql.ts @@ -20,7 +20,8 @@ type Documents = { "\n query PointCards($id: ID!) {\n pointGrant(id: $id) {\n id\n ...PointDetailsCard\n ...PointUserCard\n }\n }\n": typeof types.PointCardsDocument, "\n fragment PointDetailsCard on Point {\n points\n description\n grantedAt\n }\n": typeof types.PointDetailsCardFragmentDoc, "\n fragment PointUserCard on Point {\n user {\n id\n name\n }\n }\n": typeof types.PointUserCardFragmentDoc, - "\n query PointsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n $where: PointWhereInput\n ) {\n points(first: $first, after: $after, last: $last, before: $before, where: $where, orderBy: { field: GRANTED_AT, direction: DESC }) {\n edges {\n node {\n id\n user {\n id\n name\n }\n points\n description\n grantedAt\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n": typeof types.PointsTableDocument, + "\n query PointsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n $where: PointWhereInput\n ) {\n points(\n first: $first\n after: $after\n last: $last\n before: $before\n where: $where\n orderBy: { field: GRANTED_AT, direction: DESC }\n ) {\n edges {\n node {\n id\n ...PointsTableRow\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": typeof types.PointsTableDocument, + "\n fragment PointsTableRow on Point {\n id\n user {\n id\n name\n }\n points\n description\n grantedAt\n }\n": typeof types.PointsTableRowFragmentDoc, "\n query SubmissionHeader($id: ID!) {\n submission(id: $id) {\n id\n status\n submittedAt\n }\n }\n": typeof types.SubmissionHeaderDocument, "\n fragment SubmissionResultCard on Submission {\n queryResult {\n columns\n rows\n matchAnswer\n }\n question {\n id\n }\n }\n": typeof types.SubmissionResultCardFragmentDoc, "\n query SubmissionCards($id: ID!) {\n submission(id: $id) {\n id\n ...SubmissionDetailsCard\n ...SubmissionUserCard\n ...SubmissionResultCard\n }\n }\n": typeof types.SubmissionCardsDocument, @@ -102,7 +103,8 @@ const documents: Documents = { "\n query PointCards($id: ID!) {\n pointGrant(id: $id) {\n id\n ...PointDetailsCard\n ...PointUserCard\n }\n }\n": types.PointCardsDocument, "\n fragment PointDetailsCard on Point {\n points\n description\n grantedAt\n }\n": types.PointDetailsCardFragmentDoc, "\n fragment PointUserCard on Point {\n user {\n id\n name\n }\n }\n": types.PointUserCardFragmentDoc, - "\n query PointsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n $where: PointWhereInput\n ) {\n points(first: $first, after: $after, last: $last, before: $before, where: $where, orderBy: { field: GRANTED_AT, direction: DESC }) {\n edges {\n node {\n id\n user {\n id\n name\n }\n points\n description\n grantedAt\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n": types.PointsTableDocument, + "\n query PointsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n $where: PointWhereInput\n ) {\n points(\n first: $first\n after: $after\n last: $last\n before: $before\n where: $where\n orderBy: { field: GRANTED_AT, direction: DESC }\n ) {\n edges {\n node {\n id\n ...PointsTableRow\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": types.PointsTableDocument, + "\n fragment PointsTableRow on Point {\n id\n user {\n id\n name\n }\n points\n description\n grantedAt\n }\n": types.PointsTableRowFragmentDoc, "\n query SubmissionHeader($id: ID!) {\n submission(id: $id) {\n id\n status\n submittedAt\n }\n }\n": types.SubmissionHeaderDocument, "\n fragment SubmissionResultCard on Submission {\n queryResult {\n columns\n rows\n matchAnswer\n }\n question {\n id\n }\n }\n": types.SubmissionResultCardFragmentDoc, "\n query SubmissionCards($id: ID!) {\n submission(id: $id) {\n id\n ...SubmissionDetailsCard\n ...SubmissionUserCard\n ...SubmissionResultCard\n }\n }\n": types.SubmissionCardsDocument, @@ -219,7 +221,11 @@ export function graphql(source: "\n fragment PointUserCard on Point {\n user /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query PointsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n $where: PointWhereInput\n ) {\n points(first: $first, after: $after, last: $last, before: $before, where: $where, orderBy: { field: GRANTED_AT, direction: DESC }) {\n edges {\n node {\n id\n user {\n id\n name\n }\n points\n description\n grantedAt\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n"): (typeof documents)["\n query PointsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n $where: PointWhereInput\n ) {\n points(first: $first, after: $after, last: $last, before: $before, where: $where, orderBy: { field: GRANTED_AT, direction: DESC }) {\n edges {\n node {\n id\n user {\n id\n name\n }\n points\n description\n grantedAt\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n"]; +export function graphql(source: "\n query PointsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n $where: PointWhereInput\n ) {\n points(\n first: $first\n after: $after\n last: $last\n before: $before\n where: $where\n orderBy: { field: GRANTED_AT, direction: DESC }\n ) {\n edges {\n node {\n id\n ...PointsTableRow\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n"): (typeof documents)["\n query PointsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n $where: PointWhereInput\n ) {\n points(\n first: $first\n after: $after\n last: $last\n before: $before\n where: $where\n orderBy: { field: GRANTED_AT, direction: DESC }\n ) {\n edges {\n node {\n id\n ...PointsTableRow\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment PointsTableRow on Point {\n id\n user {\n id\n name\n }\n points\n description\n grantedAt\n }\n"): (typeof documents)["\n fragment PointsTableRow on Point {\n id\n user {\n id\n name\n }\n points\n description\n grantedAt\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/gql/graphql.ts b/gql/graphql.ts index a02e652..45bb4d2 100644 --- a/gql/graphql.ts +++ b/gql/graphql.ts @@ -49,6 +49,17 @@ export type CreateGroupInput = { scopeSetIDs?: InputMaybe>; }; +/** + * CreatePointInput is used for create Point object. + * Input was generated by ent. + */ +export type CreatePointInput = { + description?: InputMaybe; + grantedAt?: InputMaybe; + points?: InputMaybe; + userID: Scalars['ID']['input']; +}; + /** * CreateQuestionInput is used for create Question object. * Input was generated by ent. @@ -388,6 +399,8 @@ export type Mutation = { createDatabase: Database; /** Create a new group. */ createGroup?: Maybe; + /** Create a new point record for a user. */ + createPoint?: Maybe; /** Create a question. */ createQuestion: Question; /** Create a new scope set. */ @@ -440,6 +453,11 @@ export type MutationCreateGroupArgs = { }; +export type MutationCreatePointArgs = { + input: CreatePointInput; +}; + + export type MutationCreateQuestionArgs = { input: CreateQuestionInput; }; @@ -1574,7 +1592,12 @@ export type PointsTableQueryVariables = Exact<{ }>; -export type PointsTableQuery = { __typename?: 'Query', points: { __typename?: 'PointConnection', totalCount: number, edges?: Array<{ __typename?: 'PointEdge', node?: { __typename?: 'Point', id: string, points: number, description?: string | null, grantedAt: string, user: { __typename?: 'User', id: string, name: string } } | null } | null> | null, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: any | null, startCursor?: any | null } } }; +export type PointsTableQuery = { __typename?: 'Query', points: { __typename?: 'PointConnection', totalCount: number, edges?: Array<{ __typename?: 'PointEdge', node?: ( + { __typename?: 'Point', id: string } + & { ' $fragmentRefs'?: { 'PointsTableRowFragment': PointsTableRowFragment } } + ) | null } | null> | null, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: any | null } } }; + +export type PointsTableRowFragment = { __typename?: 'Point', id: string, points: number, description?: string | null, grantedAt: string, user: { __typename?: 'User', id: string, name: string } } & { ' $fragmentName'?: 'PointsTableRowFragment' }; export type SubmissionHeaderQueryVariables = Exact<{ id: Scalars['ID']['input']; @@ -2016,6 +2039,7 @@ export type BasicUserInfoQuery = { __typename?: 'Query', me: { __typename?: 'Use export const PointDetailsCardFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointDetailsCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Point"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"grantedAt"}}]}}]} as unknown as DocumentNode; export const PointUserCardFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointUserCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Point"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; +export const PointsTableRowFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointsTableRow"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Point"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"grantedAt"}}]}}]} as unknown as DocumentNode; export const SubmissionResultCardFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SubmissionResultCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Submission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"queryResult"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"columns"}},{"kind":"Field","name":{"kind":"Name","value":"rows"}},{"kind":"Field","name":{"kind":"Name","value":"matchAnswer"}}]}},{"kind":"Field","name":{"kind":"Name","value":"question"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const SubmissionDetailsCardFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SubmissionDetailsCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Submission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"submittedCode"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]} as unknown as DocumentNode; export const SubmissionUserCardFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SubmissionUserCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Submission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; @@ -2040,7 +2064,7 @@ export const EventByIdDocument = {"kind":"Document","definitions":[{"kind":"Oper export const EventsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EventsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Cursor"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Cursor"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"where"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"EventWhereInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"Variable","name":{"kind":"Name","value":"where"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"TRIGGERED_AT"}},{"kind":"ObjectField","name":{"kind":"Name","value":"direction"},"value":{"kind":"EnumValue","value":"DESC"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"triggeredAt"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}}]}}]}}]}}]} as unknown as DocumentNode; export const PointHeaderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PointHeader"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pointGrant"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"grantedAt"}}]}}]}}]} as unknown as DocumentNode; export const PointCardsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PointCards"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pointGrant"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"PointDetailsCard"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"PointUserCard"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointDetailsCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Point"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"grantedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointUserCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Point"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; -export const PointsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PointsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Cursor"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Cursor"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"where"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PointWhereInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"points"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"Variable","name":{"kind":"Name","value":"where"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"GRANTED_AT"}},{"kind":"ObjectField","name":{"kind":"Name","value":"direction"},"value":{"kind":"EnumValue","value":"DESC"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"grantedAt"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}}]}}]}}]}}]} as unknown as DocumentNode; +export const PointsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PointsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Cursor"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Cursor"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"where"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PointWhereInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"points"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"Variable","name":{"kind":"Name","value":"where"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"GRANTED_AT"}},{"kind":"ObjectField","name":{"kind":"Name","value":"direction"},"value":{"kind":"EnumValue","value":"DESC"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"PointsTableRow"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PointsTableRow"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Point"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"grantedAt"}}]}}]} as unknown as DocumentNode; export const SubmissionHeaderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SubmissionHeader"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"submission"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"submittedAt"}}]}}]}}]} as unknown as DocumentNode; export const SubmissionCardsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SubmissionCards"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"submission"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SubmissionDetailsCard"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SubmissionUserCard"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SubmissionResultCard"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SubmissionDetailsCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Submission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"submittedCode"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SubmissionUserCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Submission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SubmissionResultCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Submission"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"queryResult"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"columns"}},{"kind":"Field","name":{"kind":"Name","value":"rows"}},{"kind":"Field","name":{"kind":"Name","value":"matchAnswer"}}]}},{"kind":"Field","name":{"kind":"Name","value":"question"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const SubmissionsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SubmissionsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Cursor"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"submissions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"EnumValue","value":"SUBMITTED_AT"}},{"kind":"ObjectField","name":{"kind":"Name","value":"direction"},"value":{"kind":"EnumValue","value":"DESC"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"submittedCode"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"question"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/schema.graphql b/schema.graphql index 2f1ffc5..affe9c4 100644 --- a/schema.graphql +++ b/schema.graphql @@ -43,6 +43,17 @@ input CreateGroupInput { scopeSetIDs: [ID!] } +""" +CreatePointInput is used for create Point object. +Input was generated by ent. +""" +input CreatePointInput { + description: String + grantedAt: Time + points: Int + userID: ID! +} + """ CreateQuestionInput is used for create Question object. Input was generated by ent. @@ -383,6 +394,8 @@ type Mutation { createDatabase(input: CreateDatabaseInput!): Database! """Create a new group.""" createGroup(input: CreateGroupInput!): Group + """Create a new point record for a user.""" + createPoint(input: CreatePointInput!): Point """Create a question.""" createQuestion(input: CreateQuestionInput!): Question! """Create a new scope set.""" From becaf7a42f53739e2841aa14d246f5d7eca50005 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Thu, 23 Oct 2025 23:14:09 +0800 Subject: [PATCH 2/4] feat(points): allow granting points --- .../points/_components/create.tsx | 129 ++++++++++++++++ .../points/_components/data-table.tsx | 41 +++-- .../points/_components/update-form.tsx | 146 ++++++++++++++++++ .../(activity-management)/points/page.tsx | 2 + gql/gql.ts | 18 +++ gql/graphql.ts | 22 +++ 6 files changed, 337 insertions(+), 21 deletions(-) create mode 100644 app/(admin)/(activity-management)/points/_components/create.tsx create mode 100644 app/(admin)/(activity-management)/points/_components/update-form.tsx diff --git a/app/(admin)/(activity-management)/points/_components/create.tsx b/app/(admin)/(activity-management)/points/_components/create.tsx new file mode 100644 index 0000000..9bff7e6 --- /dev/null +++ b/app/(admin)/(activity-management)/points/_components/create.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { buttonVariants } from "@/components/ui/button"; +import { ConfirmationDialog } from "@/components/ui/confirmation-dialog"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { graphql } from "@/gql"; +import { useDialogCloseConfirmation } from "@/hooks/use-dialog-close-confirmation"; +import { useMutation } from "@apollo/client/react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { POINTS_TABLE_QUERY } from "./data-table"; +import { UpdatePointsForm } from "./update-form"; + +const CREATE_POINT_MUTATION = graphql(` + mutation CreatePoint($input: CreatePointInput!) { + createPoint(input: $input) { + id + } + } +`); + +export function CreatePointTrigger() { + const [open, setOpen] = useState(false); + const [isFormDirty, setIsFormDirty] = useState(false); + + const { + showConfirmation, + handleDialogOpenChange, + handleConfirmClose, + handleCancelClose, + } = useDialogCloseConfirmation({ + isDirty: isFormDirty, + setOpen, + onConfirmedClose: () => { + setIsFormDirty(false); + }, + }); + + const handleFormStateChange = (isDirty: boolean) => { + setIsFormDirty(isDirty); + }; + + const handleCompleted = () => { + setIsFormDirty(false); + setOpen(false); + }; + + return ( + <> + + 給予點數 + + + + {}} + onConfirm={handleConfirmClose} + onCancel={handleCancelClose} + /> + + ); +} + +function CreatePointDialogContent({ + onCompleted, + onFormStateChange, +}: { + onCompleted: () => void; + onFormStateChange: (isDirty: boolean) => void; +}) { + const [createPoint] = useMutation(CREATE_POINT_MUTATION, { + refetchQueries: [POINTS_TABLE_QUERY], + + onError(error) { + toast.error("給予點數失敗", { + description: error.message, + }); + }, + + onCompleted() { + toast.success("給予點數成功"); + onCompleted(); + }, + }); + + const onSubmit = (formData: { userID: string; points: number; description?: string }) => { + createPoint({ + variables: { + input: { + userID: formData.userID, + points: formData.points, + description: formData.description, + }, + }, + }); + }; + + return ( + + + 給予點數 + + 給一個使用者手動發放點數。 + + + + + ); +} diff --git a/app/(admin)/(activity-management)/points/_components/data-table.tsx b/app/(admin)/(activity-management)/points/_components/data-table.tsx index 3044fd6..ac4e8a8 100644 --- a/app/(admin)/(activity-management)/points/_components/data-table.tsx +++ b/app/(admin)/(activity-management)/points/_components/data-table.tsx @@ -2,13 +2,13 @@ import { CursorDataTable } from "@/components/data-table/cursor"; import type { Direction } from "@/components/data-table/pagination"; +import { graphql, useFragment as readFragment } from "@/gql"; import { useSuspenseQuery } from "@apollo/client/react"; import type { VariablesOf } from "@graphql-typed-document-node/core"; import { useState } from "react"; import { columns, type Point } from "./data-table-columns"; -import { graphql, useFragment as readFragment } from "@/gql"; -const POINTS_TABLE_QUERY = graphql(` +export const POINTS_TABLE_QUERY = graphql(` query PointsTable( $first: Int $after: Cursor @@ -69,27 +69,26 @@ export function PointsDataTable({ query }: { query?: string }) { variables, }); - const pointsList = - data?.points.edges - ?.map((edge) => { - const node = edge?.node; - if (!node) return null; + const pointsList = data?.points.edges + ?.map((edge) => { + const node = edge?.node; + if (!node) return null; - const point = readFragment(POINTS_TABLE_ROW_FRAGEMENT, node); + const point = readFragment(POINTS_TABLE_ROW_FRAGEMENT, node); - if (!point) return null; - return { - id: point.id, - user: { - id: point.user.id, - name: point.user.name, - }, - points: point.points, - description: point.description ?? "", - grantedAt: point.grantedAt, - } satisfies Point; - }) - .filter((point) => point !== null) ?? []; + if (!point) return null; + return { + id: point.id, + user: { + id: point.user.id, + name: point.user.name, + }, + points: point.points, + description: point.description ?? "", + grantedAt: point.grantedAt, + } satisfies Point; + }) + .filter((point) => point !== null) ?? []; const pageInfo = data?.points.pageInfo; diff --git a/app/(admin)/(activity-management)/points/_components/update-form.tsx b/app/(admin)/(activity-management)/points/_components/update-form.tsx new file mode 100644 index 0000000..6c3029b --- /dev/null +++ b/app/(admin)/(activity-management)/points/_components/update-form.tsx @@ -0,0 +1,146 @@ +import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Spinner } from "@/components/ui/spinner"; +import { Textarea } from "@/components/ui/textarea"; +import { UpdateFormBody } from "@/components/update-modal/form-body"; +import type { UpdateFormBaseProps } from "@/components/update-modal/types"; +import { graphql } from "@/gql"; +import { skipToken, useQuery } from "@apollo/client/react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useDebouncedValue } from "foxact/use-debounced-value"; +import { useForm, useWatch } from "react-hook-form"; +import { z } from "zod"; + +export const formSchema = z.object({ + userID: z.string(), + points: z.number(), + description: z.string().optional(), +}); + +export type UpdatePointsFormData = z.infer; + +export interface UpdatePointsFormProps extends Omit>, "onSubmit"> { + onSubmit: (newValues: { + userID: string; + points: number; + description?: string; + }) => void; +} + +const UPDATE_POINTS_FORM_USER_INFO_QUERY = graphql(` + query UpdatePointsFormUserInfo($id: ID!) { + user(id: $id) { + id + name + email + } + } +`); + +export function UpdatePointsForm({ + defaultValues, + onSubmit, + action, + onFormStateChange, +}: UpdatePointsFormProps) { + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + userID: "", + points: 0, + description: "", + ...defaultValues, + } as z.infer, + }); + + const userID = useWatch({ control: form.control, name: "userID" }); + const userIDDebounced = useDebouncedValue(userID, 200); + + const { data: userInfoData, loading } = useQuery( + UPDATE_POINTS_FORM_USER_INFO_QUERY, + userIDDebounced + ? { + variables: { + id: userIDDebounced, + }, + errorPolicy: "ignore", + } + : skipToken, + ); + + const handleSubmit = (data: z.infer) => { + onSubmit({ + userID: data.userID, + points: data.points as number, + description: data.description, + }); + }; + + return ( + + ( + + 使用者 ID + + + + + 選擇要發放點數的使用者。可以到使用者管理頁面確認對應代號。
+ {loading ? : null} + {userInfoData?.user + ? `您正要發放給:${userInfoData.user.name} (${userInfoData.user.email})` + : "您輸入的使用者 ID 不存在。"} +
+ +
+ )} + /> + + ( + + 點數 + + + + 要發放給使用者的點數數量。 + + + )} + /> + + ( + + 備註(可選) + +