([])
const [totalPages, setTotalPages] = useState(1)
+ const [sortBy, setSortBy] = useState(initialSortBy)
+ const [order, setOrder] = useState(initialOrder)
const debounceSearch = useMemo(() => debounce((q) => setDebouncedQuery(q), 400), [])
@@ -45,18 +51,20 @@ const MyMentorshipPage: React.FC = () => {
const params = new URLSearchParams()
if (searchQuery) params.set('q', searchQuery)
if (page > 1) params.set('page', String(page))
+ if (sortBy && sortBy !== 'default') params.set('sortBy', sortBy)
+ if (order && order !== 'desc') params.set('order', order)
const nextUrl = params.toString() ? `?${params}` : globalThis.location.pathname
if (globalThis.location.search !== `?${params}`) {
router.push(nextUrl, { scroll: false })
}
- }, [searchQuery, page, router])
+ }, [searchQuery, page, sortBy, order, router])
const {
data: programData,
loading: loadingPrograms,
error,
} = useQuery(GetMyProgramsDocument, {
- variables: { search: debouncedQuery, page, limit: 24 },
+ variables: { search: debouncedQuery, page, limit: 24, sortBy, order },
fetchPolicy: 'cache-and-network',
errorPolicy: 'all',
})
@@ -83,6 +91,16 @@ const MyMentorshipPage: React.FC = () => {
const handleCreate = () => router.push('/my/mentorship/programs/create')
+ const handleSortChange = (value: string) => {
+ setSortBy(value)
+ setPage(1)
+ }
+
+ const handleOrderChange = (value: string) => {
+ setOrder(value)
+ setPage(1)
+ }
+
if (!userName) {
return
}
@@ -127,6 +145,15 @@ const MyMentorshipPage: React.FC = () => {
searchQuery={searchQuery}
searchPlaceholder="Search your programs"
indexName="my-programs"
+ sortChildren={
+
+ }
>
{programs.length === 0 ? (
diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx
index 21934208c5..c7180a8dc8 100644
--- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx
+++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx
@@ -1,14 +1,22 @@
'use client'
-import { useQuery } from '@apollo/client/react'
+import { useLazyQuery, useQuery } from '@apollo/client/react'
import { Select, SelectItem } from '@heroui/select'
+import { addToast } from '@heroui/toast'
import { useParams, useRouter, useSearchParams } from 'next/navigation'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ErrorDisplay, handleAppError } from 'app/global-error'
-import { GetModuleIssuesDocument } from 'types/__generated__/moduleQueries.generated'
+import ExportButton, { type ExportFormat } from 'components/ExportButton'
import IssuesTable, { type IssueRow } from 'components/IssuesTable'
import LoadingSpinner from 'components/LoadingSpinner'
import Pagination from 'components/Pagination'
+import { GetModuleIssuesDocument } from 'types/__generated__/moduleQueries.generated'
+import {
+ buildExportQuery,
+ downloadFile,
+ getExportErrorMessage,
+ parseExportResponse,
+} from 'utils/exportUtils'
const ITEMS_PER_PAGE = 20
const LABEL_ALL = 'all'
@@ -59,9 +67,9 @@ const IssuesPage = () => {
}
const labels = new Set()
- ;(moduleData?.issues || []).forEach((i) =>
- (i.labels || []).forEach((l: string) => labels.add(l))
- )
+ ; (moduleData?.issues || []).forEach((i) =>
+ (i.labels || []).forEach((l: string) => labels.add(l))
+ )
return Array.from(labels).sort((a, b) => a.localeCompare(b))
}, [moduleData])
@@ -90,6 +98,59 @@ const IssuesPage = () => {
[router, programKey, moduleKey]
)
+ const handleExport = useCallback(
+ async (format: ExportFormat) => {
+ try {
+ const query = buildExportQuery({
+ programKey,
+ moduleKey,
+ format,
+ label: selectedLabel !== LABEL_ALL ? selectedLabel : null,
+ })
+
+ const response = await fetch('/api/graphql', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ credentials: 'include',
+ body: JSON.stringify({ query }),
+ })
+
+ if (!response.ok) {
+ throw new Error('Export request failed')
+ }
+
+ const result = await response.json()
+
+ if (result.errors?.length) {
+ throw new Error(result.errors[0]?.message || 'Export failed')
+ }
+
+ const exportResult = parseExportResponse(result.data)
+
+ if (!exportResult) {
+ throw new Error('Invalid export response')
+ }
+
+ downloadFile(exportResult.content, exportResult.filename, exportResult.mimeType)
+
+ addToast({
+ title: 'Export Complete',
+ description: `Successfully exported ${exportResult.count} issues as ${format}`,
+ color: 'success',
+ })
+ } catch (error) {
+ const message = getExportErrorMessage(error)
+ addToast({
+ title: 'Export Failed',
+ description: message,
+ color: 'danger',
+ })
+ console.error('Export failed:', error)
+ }
+ },
+ [programKey, moduleKey, selectedLabel]
+ )
+
if (loading) return
if (!moduleData)
return
@@ -129,23 +190,27 @@ const IssuesPage = () => {
))}
+
-
-
-
- {/* Pagination Controls */}
-
+
+
+
+
)
}
diff --git a/frontend/src/components/ExportButton.tsx b/frontend/src/components/ExportButton.tsx
new file mode 100644
index 0000000000..63c9db1128
--- /dev/null
+++ b/frontend/src/components/ExportButton.tsx
@@ -0,0 +1,73 @@
+'use client'
+
+import { Button } from '@heroui/button'
+import { Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from '@heroui/react'
+import type React from 'react'
+import { useState } from 'react'
+import { FiDownload } from 'react-icons/fi'
+
+export type ExportFormat = 'CSV' | 'JSON'
+
+interface ExportButtonProps {
+ onExport: (format: ExportFormat) => Promise
+ isDisabled?: boolean
+ className?: string
+}
+
+/**
+ * Export button component with format selection dropdown.
+ * Shows loading state during export and handles format selection.
+ */
+const ExportButton: React.FC = ({ onExport, isDisabled = false, className }) => {
+ const [isExporting, setIsExporting] = useState(false)
+
+ const handleExport = async (format: ExportFormat) => {
+ if (isExporting || isDisabled) return
+
+ setIsExporting(true)
+ try {
+ await onExport(format)
+ } finally {
+ setIsExporting(false)
+ }
+ }
+
+ return (
+
+
+ }
+ className={className}
+ aria-label="Export issues"
+ >
+ Export
+
+
+ handleExport(key as ExportFormat)}
+ disabledKeys={isExporting ? ['CSV', 'JSON'] : []}
+ >
+ 📊}
+ >
+ Export as CSV
+
+ 📄}
+ >
+ Export as JSON
+
+
+
+ )
+}
+
+export default ExportButton
diff --git a/frontend/src/server/queries/programsQueries.ts b/frontend/src/server/queries/programsQueries.ts
index 265ce7b71e..1b4f0e2f85 100644
--- a/frontend/src/server/queries/programsQueries.ts
+++ b/frontend/src/server/queries/programsQueries.ts
@@ -1,8 +1,8 @@
import { gql } from '@apollo/client'
export const GET_MY_PROGRAMS = gql`
- query GetMyPrograms($search: String, $page: Int, $limit: Int) {
- myPrograms(search: $search, page: $page, limit: $limit) {
+ query GetMyPrograms($search: String, $page: Int, $limit: Int, $sortBy: String, $order: String) {
+ myPrograms(search: $search, page: $page, limit: $limit, sortBy: $sortBy, order: $order) {
currentPage
totalPages
programs {
diff --git a/frontend/src/types/__generated__/programsQueries.generated.ts b/frontend/src/types/__generated__/programsQueries.generated.ts
index d9a2162bad..1054289bd9 100644
--- a/frontend/src/types/__generated__/programsQueries.generated.ts
+++ b/frontend/src/types/__generated__/programsQueries.generated.ts
@@ -5,6 +5,8 @@ export type GetMyProgramsQueryVariables = Types.Exact<{
search?: Types.InputMaybe;
page?: Types.InputMaybe;
limit?: Types.InputMaybe;
+ sortBy?: Types.InputMaybe;
+ order?: Types.InputMaybe;
}>;
@@ -32,7 +34,7 @@ export type GetProgramAdminDetailsQueryVariables = Types.Exact<{
export type GetProgramAdminDetailsQuery = { getProgram: { __typename: 'ProgramNode', id: string, key: string, name: string, startedAt: any, endedAt: any, admins: Array<{ __typename: 'MentorNode', id: string, login: string, name: string, avatarUrl: string }> | null } | null };
-export const GetMyProgramsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMyPrograms"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"search"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myPrograms"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"search"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentPage"}},{"kind":"Field","name":{"kind":"Name","value":"totalPages"}},{"kind":"Field","name":{"kind":"Name","value":"programs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"startedAt"}},{"kind":"Field","name":{"kind":"Name","value":"endedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userRole"}}]}}]}}]}}]} as unknown as DocumentNode;
-export const GetProgramDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProgramDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getProgram"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"programKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"menteesLimit"}},{"kind":"Field","name":{"kind":"Name","value":"experienceLevels"}},{"kind":"Field","name":{"kind":"Name","value":"startedAt"}},{"kind":"Field","name":{"kind":"Name","value":"endedAt"}},{"kind":"Field","name":{"kind":"Name","value":"domains"}},{"kind":"Field","name":{"kind":"Name","value":"tags"}},{"kind":"Field","name":{"kind":"Name","value":"admins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}}]}}]}}]}}]} as unknown as DocumentNode;
-export const GetProgramAndModulesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProgramAndModules"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getProgram"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"programKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"menteesLimit"}},{"kind":"Field","name":{"kind":"Name","value":"experienceLevels"}},{"kind":"Field","name":{"kind":"Name","value":"startedAt"}},{"kind":"Field","name":{"kind":"Name","value":"endedAt"}},{"kind":"Field","name":{"kind":"Name","value":"domains"}},{"kind":"Field","name":{"kind":"Name","value":"tags"}},{"kind":"Field","name":{"kind":"Name","value":"admins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"recentMilestones"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"openIssuesCount"}},{"kind":"Field","name":{"kind":"Name","value":"closedIssuesCount"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"repositoryName"}},{"kind":"Field","name":{"kind":"Name","value":"organizationName"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"getProgramModules"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"programKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"experienceLevel"}},{"kind":"Field","name":{"kind":"Name","value":"startedAt"}},{"kind":"Field","name":{"kind":"Name","value":"endedAt"}},{"kind":"Field","name":{"kind":"Name","value":"domains"}},{"kind":"Field","name":{"kind":"Name","value":"tags"}},{"kind":"Field","name":{"kind":"Name","value":"labels"}},{"kind":"Field","name":{"kind":"Name","value":"mentors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mentees"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}}]}}]}}]}}]} as unknown as DocumentNode;
-export const GetProgramAdminDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProgramAdminDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getProgram"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"programKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"startedAt"}},{"kind":"Field","name":{"kind":"Name","value":"endedAt"}},{"kind":"Field","name":{"kind":"Name","value":"admins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}}]}}]}}]}}]} as unknown as DocumentNode;
\ No newline at end of file
+export const GetMyProgramsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GetMyPrograms" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "search" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "page" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "sortBy" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "order" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "myPrograms" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "search" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "search" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "page" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "page" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "sortBy" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "sortBy" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "order" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "order" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "currentPage" } }, { "kind": "Field", "name": { "kind": "Name", "value": "totalPages" } }, { "kind": "Field", "name": { "kind": "Name", "value": "programs" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "status" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "startedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "userRole" } }] } }] } }] } }] } as unknown as DocumentNode;
+export const GetProgramDetailsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GetProgramDetails" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "programKey" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "getProgram" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "programKey" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "programKey" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "status" } }, { "kind": "Field", "name": { "kind": "Name", "value": "menteesLimit" } }, { "kind": "Field", "name": { "kind": "Name", "value": "experienceLevels" } }, { "kind": "Field", "name": { "kind": "Name", "value": "startedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "domains" } }, { "kind": "Field", "name": { "kind": "Name", "value": "tags" } }, { "kind": "Field", "name": { "kind": "Name", "value": "admins" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "login" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "avatarUrl" } }] } }] } }] } }] } as unknown as DocumentNode;
+export const GetProgramAndModulesDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GetProgramAndModules" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "programKey" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "getProgram" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "programKey" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "programKey" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "status" } }, { "kind": "Field", "name": { "kind": "Name", "value": "menteesLimit" } }, { "kind": "Field", "name": { "kind": "Name", "value": "experienceLevels" } }, { "kind": "Field", "name": { "kind": "Name", "value": "startedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "domains" } }, { "kind": "Field", "name": { "kind": "Name", "value": "tags" } }, { "kind": "Field", "name": { "kind": "Name", "value": "admins" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "login" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "avatarUrl" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "recentMilestones" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "title" } }, { "kind": "Field", "name": { "kind": "Name", "value": "state" } }, { "kind": "Field", "name": { "kind": "Name", "value": "openIssuesCount" } }, { "kind": "Field", "name": { "kind": "Name", "value": "closedIssuesCount" } }, { "kind": "Field", "name": { "kind": "Name", "value": "createdAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "repositoryName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "organizationName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "url" } }, { "kind": "Field", "name": { "kind": "Name", "value": "author" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "login" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "avatarUrl" } }] } }] } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "getProgramModules" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "programKey" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "programKey" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "experienceLevel" } }, { "kind": "Field", "name": { "kind": "Name", "value": "startedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "domains" } }, { "kind": "Field", "name": { "kind": "Name", "value": "tags" } }, { "kind": "Field", "name": { "kind": "Name", "value": "labels" } }, { "kind": "Field", "name": { "kind": "Name", "value": "mentors" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "login" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "avatarUrl" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "mentees" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "login" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "avatarUrl" } }] } }] } }] } }] } as unknown as DocumentNode;
+export const GetProgramAdminDetailsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GetProgramAdminDetails" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "programKey" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "getProgram" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "programKey" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "programKey" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "key" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "startedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "admins" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "login" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "avatarUrl" } }] } }] } }] } }] } as unknown as DocumentNode;
\ No newline at end of file
diff --git a/frontend/src/utils/exportUtils.ts b/frontend/src/utils/exportUtils.ts
new file mode 100644
index 0000000000..8496ca7343
--- /dev/null
+++ b/frontend/src/utils/exportUtils.ts
@@ -0,0 +1,103 @@
+/**
+ * Export utilities for downloading issue data in CSV/JSON formats.
+ *
+ * This module provides functions to trigger GraphQL export queries
+ * and download the resulting data as files.
+ */
+
+export type ExportFormat = 'CSV' | 'JSON'
+
+export interface ExportOptions {
+ programKey: string
+ moduleKey: string
+ format: ExportFormat
+ label?: string | null
+}
+
+export interface ExportResult {
+ content: string
+ filename: string
+ mimeType: string
+ count: number
+}
+
+/**
+ * Triggers a file download in the browser.
+ *
+ * @param content - The file content as a string
+ * @param filename - The name for the downloaded file
+ * @param mimeType - The MIME type of the file
+ */
+export function downloadFile(content: string, filename: string, mimeType: string): void {
+ const blob = new Blob([content], { type: mimeType })
+ const url = window.URL.createObjectURL(blob)
+
+ const link = document.createElement('a')
+ link.href = url
+ link.download = filename
+ document.body.appendChild(link)
+ link.click()
+
+ // Cleanup
+ document.body.removeChild(link)
+ window.URL.revokeObjectURL(url)
+}
+
+/**
+ * Builds the GraphQL query for exporting issues.
+ *
+ * @param options - Export options including format and filters
+ * @returns GraphQL query string
+ */
+export function buildExportQuery(options: ExportOptions): string {
+ const { programKey, moduleKey, format, label } = options
+ const labelArg = label && label !== 'all' ? `, label: "${label}"` : ''
+
+ return `
+ query ExportModuleIssues {
+ getModule(programKey: "${programKey}", moduleKey: "${moduleKey}") {
+ exportIssues(format: ${format}${labelArg}) {
+ content
+ filename
+ mimeType
+ count
+ }
+ }
+ }
+ `
+}
+
+/**
+ * Parse the GraphQL response to extract export result.
+ *
+ * @param data - GraphQL response data
+ * @returns ExportResult or null if not found
+ */
+export function parseExportResponse(data: unknown): ExportResult | null {
+ if (!data || typeof data !== 'object') return null
+
+ const responseData = data as { getModule?: { exportIssues?: ExportResult } }
+
+ if (!responseData.getModule?.exportIssues) return null
+
+ return responseData.getModule.exportIssues
+}
+
+/**
+ * Get user-friendly error message for export failures.
+ *
+ * @param error - The error object
+ * @returns Human-readable error message
+ */
+export function getExportErrorMessage(error: unknown): string {
+ if (error instanceof Error) {
+ if (error.message.includes('network')) {
+ return 'Network error. Please check your connection and try again.'
+ }
+ if (error.message.includes('timeout')) {
+ return 'Export timed out. Try exporting fewer issues.'
+ }
+ return error.message
+ }
+ return 'An unexpected error occurred during export.'
+}
diff --git a/frontend/src/utils/sortingOptions.ts b/frontend/src/utils/sortingOptions.ts
index 4acc11b877..9261b20e7e 100644
--- a/frontend/src/utils/sortingOptions.ts
+++ b/frontend/src/utils/sortingOptions.ts
@@ -7,3 +7,11 @@ export const sortOptionsProject = [
{ label: 'Name', key: 'name' },
{ label: 'Stars', key: 'stars_count' },
]
+
+export const sortOptionsMentorshipPrograms = [
+ { label: 'Newest', key: 'default' },
+ { label: 'Name', key: 'name' },
+ { label: 'Start Date', key: 'started_at' },
+ { label: 'End Date', key: 'ended_at' },
+]
+