diff --git a/backend/apps/github/api/internal/nodes/issue.py b/backend/apps/github/api/internal/nodes/issue.py index 7f0c5be076..40c1bddf66 100644 --- a/backend/apps/github/api/internal/nodes/issue.py +++ b/backend/apps/github/api/internal/nodes/issue.py @@ -1,8 +1,11 @@ """GitHub issue GraphQL node.""" +from datetime import datetime + import strawberry import strawberry_django from django.db.models import Exists, OuterRef +from strawberry.types import Info from apps.github.api.internal.nodes.pull_request import PullRequestNode from apps.github.api.internal.nodes.user import UserNode @@ -71,3 +74,11 @@ def interested_users(self, root: Issue) -> list[UserNode]: "user__login" ) ] + + @strawberry.field + def task_deadline(self, root: Issue, info: Info) -> datetime | None: + """Return the deadline for the latest assigned task linked to this issue.""" + mapping = getattr(info.context, "task_deadlines_by_issue", None) + if mapping is None: + return None + return mapping.get(root.number) diff --git a/backend/apps/mentorship/api/internal/nodes/module.py b/backend/apps/mentorship/api/internal/nodes/module.py index 56edf42a32..e84806b656 100644 --- a/backend/apps/mentorship/api/internal/nodes/module.py +++ b/backend/apps/mentorship/api/internal/nodes/module.py @@ -3,6 +3,7 @@ from datetime import datetime import strawberry +from strawberry.types import Info from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.issue import IssueNode @@ -79,9 +80,37 @@ def project_name(self) -> str | None: @strawberry.field def issues( - self, limit: int = 20, offset: int = 0, label: str | None = None + self, info: Info, limit: int = 20, offset: int = 0, label: str | None = None ) -> list[IssueNode]: """Return paginated issues linked to this module, optionally filtered by label.""" + info.context.current_module = self + + # BULK load data + deadline_rows = ( + Task.objects.filter(module=self, deadline_at__isnull=False) + .order_by("issue__number", "-assigned_at") + .values("issue__number", "deadline_at") + ) + assigned_rows = ( + Task.objects.filter(module=self, assigned_at__isnull=False) + .order_by("issue__number", "-assigned_at") + .values("issue__number", "assigned_at") + ) + + deadline_map = {} + assigned_map = {} + + for row in deadline_rows: + num = row["issue__number"] + if num not in deadline_map: + deadline_map[num] = row["deadline_at"] + for row in assigned_rows: + num = row["issue__number"] + if num not in assigned_map: + assigned_map[num] = row["assigned_at"] + + info.context.task_deadlines_by_issue = deadline_map + info.context.task_assigned_at_by_issue = assigned_map if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: return [] @@ -116,8 +145,10 @@ def available_labels(self) -> list[str]: return sorted(label_names) @strawberry.field - def issue_by_number(self, number: int) -> IssueNode | None: + def issue_by_number(self, info: Info, number: int) -> IssueNode | None: """Return a single issue by its GitHub number within this module's linked issues.""" + info.context.current_module = self + return ( self.issues.select_related("repository", "author") .prefetch_related("assignees", "labels") @@ -139,8 +170,13 @@ def interested_users(self, issue_number: int) -> list[UserNode]: return [i.user for i in interests] @strawberry.field - def task_deadline(self, issue_number: int) -> datetime | None: + def task_deadline(self, info: Info, issue_number: int) -> datetime | None: """Return the deadline for the latest assigned task linked to this module and issue.""" + mapping = getattr(info.context, "task_deadlines_by_issue", None) + if mapping is not None: + return mapping.get(issue_number) + + # fallback (single issue query) return ( Task.objects.filter( module=self, @@ -153,8 +189,12 @@ def task_deadline(self, issue_number: int) -> datetime | None: ) @strawberry.field - def task_assigned_at(self, issue_number: int) -> datetime | None: - """Return the latest assignment time for tasks linked to this module and issue number.""" + def task_assigned_at(self, info: Info, issue_number: int) -> datetime | None: + """Return the latest assignment time for tasks linked to this module and issue.""" + mapping = getattr(info.context, "task_assigned_at_by_issue", None) + if mapping is not None: + return mapping.get(issue_number) + return ( Task.objects.filter( module=self, diff --git a/backend/tests/apps/github/api/internal/nodes/issue_test.py b/backend/tests/apps/github/api/internal/nodes/issue_test.py index 54a6f61f62..bb243e5d81 100644 --- a/backend/tests/apps/github/api/internal/nodes/issue_test.py +++ b/backend/tests/apps/github/api/internal/nodes/issue_test.py @@ -28,6 +28,7 @@ def test_issue_node_fields(self): "pull_requests", "repository_name", "state", + "task_deadline", "title", "url", } diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx index 067dfc0da2..05d5f8b3ad 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx @@ -27,18 +27,20 @@ const ModuleIssueDetailsPage = () => { const formatDeadline = (deadline: string | null) => { if (!deadline) return { text: 'No deadline set', color: 'text-gray-600 dark:text-gray-300' } + const now = new Date() + + const todayUTC = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) + const deadlineDate = new Date(deadline) - const today = new Date() - const deadlineUTC = new Date( + const deadlineUTC = Date.UTC( deadlineDate.getUTCFullYear(), deadlineDate.getUTCMonth(), deadlineDate.getUTCDate() ) - const todayUTC = new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()) - const isOverdue = deadlineUTC < todayUTC - const daysLeft = Math.ceil((deadlineUTC.getTime() - todayUTC.getTime()) / (1000 * 60 * 60 * 24)) + const daysLeft = Math.ceil((deadlineUTC - todayUTC) / (1000 * 60 * 60 * 24)) + const isOverdue = daysLeft < 0 let statusText: string if (isOverdue) { 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..92356cff42 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 @@ -6,26 +6,58 @@ 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 IssuesTable, { type IssueRow } from 'components/IssuesTable' +import IssuesTable from 'components/IssuesTable' import LoadingSpinner from 'components/LoadingSpinner' import Pagination from 'components/Pagination' const ITEMS_PER_PAGE = 20 const LABEL_ALL = 'all' +const DEADLINE_ALL = 'all' +const DEADLINE_OPTIONS = [ + { key: 'all', label: 'All' }, + { key: 'overdue', label: 'Overdue' }, + { key: 'due-soon', label: 'Due Soon' }, + { key: 'upcoming', label: 'Upcoming' }, + { key: 'no-deadline', label: 'No Deadline' }, +] + +const getDeadlineCategory = (deadline?: string | null): string => { + if (!deadline) return 'no-deadline' + + const now = new Date() + const deadlineDate = new Date(deadline) + const nowStartUtc = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) + const deadlineStartUtc = Date.UTC( + deadlineDate.getUTCFullYear(), + deadlineDate.getUTCMonth(), + deadlineDate.getUTCDate() + ) + const diffDays = Math.floor((deadlineStartUtc - nowStartUtc) / 86400000) + + if (diffDays < 0) return 'overdue' + if (diffDays <= 7) return 'due-soon' + return 'upcoming' +} const IssuesPage = () => { const { programKey, moduleKey } = useParams<{ programKey: string; moduleKey: string }>() const router = useRouter() const searchParams = useSearchParams() const [selectedLabel, setSelectedLabel] = useState(searchParams.get('label') || LABEL_ALL) + const [selectedDeadline, setSelectedDeadline] = useState( + searchParams.get('deadline') || DEADLINE_ALL + ) const [currentPage, setCurrentPage] = useState(1) + const isDeadlineFilterActive = selectedDeadline !== DEADLINE_ALL + const MAX_ISSUES_FOR_DEADLINE_FILTER = 1000 + const { data, loading, error } = useQuery(GetModuleIssuesDocument, { variables: { programKey, moduleKey, - limit: ITEMS_PER_PAGE, - offset: (currentPage - 1) * ITEMS_PER_PAGE, + limit: isDeadlineFilterActive ? MAX_ISSUES_FOR_DEADLINE_FILTER : ITEMS_PER_PAGE, + offset: isDeadlineFilterActive ? 0 : (currentPage - 1) * ITEMS_PER_PAGE, label: selectedLabel === LABEL_ALL ? null : selectedLabel, }, skip: !programKey || !moduleKey, @@ -38,8 +70,8 @@ const IssuesPage = () => { const moduleData = data?.getModule - const moduleIssues: IssueRow[] = useMemo(() => { - return (moduleData?.issues || []).map((i) => ({ + const { moduleIssues, filteredCount } = useMemo(() => { + const allIssues = (moduleData?.issues || []).map((i) => ({ objectID: i.id, number: i.number, title: i.title, @@ -47,10 +79,26 @@ const IssuesPage = () => { isMerged: i.isMerged, labels: i.labels || [], assignees: i.assignees || [], + deadline: i.taskDeadline ?? null, })) - }, [moduleData]) - const totalPages = Math.ceil((moduleData?.issuesCount || 0) / ITEMS_PER_PAGE) + if (selectedDeadline !== DEADLINE_ALL) { + // Filter by deadline category + const filtered = allIssues.filter( + (issue) => getDeadlineCategory(issue.deadline) === selectedDeadline + ) + // Apply client-side pagination on filtered results + const start = (currentPage - 1) * ITEMS_PER_PAGE + const paginatedIssues = filtered.slice(start, start + ITEMS_PER_PAGE) + return { moduleIssues: paginatedIssues, filteredCount: filtered.length } + } + + return { moduleIssues: allIssues, filteredCount: moduleData?.issuesCount || 0 } + }, [moduleData, selectedDeadline, currentPage]) + + const totalPages = Math.ceil( + (isDeadlineFilterActive ? filteredCount : moduleData?.issuesCount || 0) / ITEMS_PER_PAGE + ) const allLabels: string[] = useMemo(() => { const serverLabels = moduleData?.availableLabels @@ -77,6 +125,18 @@ const IssuesPage = () => { router.replace(`?${params.toString()}`) } + const handleDeadlineChange = (deadline: string) => { + setSelectedDeadline(deadline) + setCurrentPage(1) + const params = new URLSearchParams(searchParams.toString()) + if (deadline === DEADLINE_ALL) { + params.delete('deadline') + } else { + params.set('deadline', deadline) + } + router.replace(`?${params.toString()}`) + } + const handlePageChange = (page: number) => { setCurrentPage(page) } @@ -97,43 +157,76 @@ const IssuesPage = () => { return (
-
+

{moduleData.name} Issues

-
- +
+
+ +
+
+ +
diff --git a/frontend/src/components/IssuesTable.tsx b/frontend/src/components/IssuesTable.tsx index 1e4805ca5e..2015ce12dd 100644 --- a/frontend/src/components/IssuesTable.tsx +++ b/frontend/src/components/IssuesTable.tsx @@ -22,6 +22,7 @@ export type IssueRow = { interface IssuesTableProps { issues: IssueRow[] showAssignee?: boolean + showDeadline?: boolean onIssueClick?: (issueNumber: number) => void issueUrl?: (issueNumber: number) => string maxVisibleLabels?: number @@ -33,6 +34,7 @@ const MAX_VISIBLE_LABELS = 5 const IssuesTable: React.FC = ({ issues, showAssignee = true, + showDeadline = false, onIssueClick, issueUrl, maxVisibleLabels = MAX_VISIBLE_LABELS, @@ -68,11 +70,42 @@ const IssuesTable: React.FC = ({ } const getColumnCount = () => { - let count = 3 + let count = 3 // Title, Status, Labels if (showAssignee) count++ + if (showDeadline) count++ return count } + const getDeadlineStatus = (deadline?: string | null) => { + if (!deadline) + return { + text: 'No Deadline', + class: 'border border-dashed border-gray-500 text-gray-500 bg-transparent', + } + + const now = new Date() + const deadlineDate = new Date(deadline) + const utcStart = (d: Date) => Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) + const diffDays = Math.floor((utcStart(deadlineDate) - utcStart(now)) / 86400000) + + if (diffDays < 0) + return { + text: 'Overdue', + class: 'bg-red-500/15 text-red-400 border border-red-500/30', + } + + if (diffDays <= 7) + return { + text: 'Due Soon', + class: 'bg-amber-500/15 text-amber-400 border border-amber-500/30', + } + + return { + text: 'Upcoming', + class: 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20', + } + } + return (
@@ -104,6 +137,14 @@ const IssuesTable: React.FC = ({ Assignee )} + {showDeadline && ( + + )} @@ -171,6 +212,21 @@ const IssuesTable: React.FC = ({ ) : null} )} + + {/* Deadline */} + {showDeadline && + (() => { + const status = getDeadlineStatus(issue.deadline) + return ( + + ) + })()} ))} {issues.length === 0 && ( diff --git a/frontend/src/server/queries/moduleQueries.ts b/frontend/src/server/queries/moduleQueries.ts index a21f879a6b..a1c884afa3 100644 --- a/frontend/src/server/queries/moduleQueries.ts +++ b/frontend/src/server/queries/moduleQueries.ts @@ -132,6 +132,7 @@ export const GET_MODULE_ISSUES = gql` state isMerged labels + taskDeadline assignees { avatarUrl login diff --git a/frontend/src/types/__generated__/graphql.ts b/frontend/src/types/__generated__/graphql.ts index c1eb07d419..67b4af003d 100644 --- a/frontend/src/types/__generated__/graphql.ts +++ b/frontend/src/types/__generated__/graphql.ts @@ -227,6 +227,7 @@ export type IssueNode = Node & { pullRequests: Array; repositoryName?: Maybe; state: Scalars['String']['output']; + taskDeadline?: Maybe; title: Scalars['String']['output']; url: Scalars['String']['output']; }; diff --git a/frontend/src/types/__generated__/moduleQueries.generated.ts b/frontend/src/types/__generated__/moduleQueries.generated.ts index 9fd6cf5802..1671d7a025 100644 --- a/frontend/src/types/__generated__/moduleQueries.generated.ts +++ b/frontend/src/types/__generated__/moduleQueries.generated.ts @@ -33,10 +33,10 @@ export type GetModuleIssuesQueryVariables = Types.Exact<{ }>; -export type GetModuleIssuesQuery = { getModule: { __typename: 'ModuleNode', name: string, issuesCount: number, availableLabels: Array, issues: Array<{ __typename: 'IssueNode', id: string, number: number, title: string, state: string, isMerged: boolean, labels: Array, assignees: Array<{ __typename: 'UserNode', avatarUrl: string, login: string, name: string }> }> } | null }; +export type GetModuleIssuesQuery = { getModule: { __typename: 'ModuleNode', name: string, issuesCount: number, availableLabels: Array, issues: Array<{ __typename: 'IssueNode', id: string, number: number, title: string, state: string, isMerged: boolean, labels: Array, taskDeadline: any | null, assignees: Array<{ __typename: 'UserNode', avatarUrl: string, login: string, name: string }> }> } | null }; export const GetModulesByProgramDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModulesByProgram"},"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":"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":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"projectName"}},{"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":"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 GetModuleByIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModuleByID"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"moduleKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"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":"getModule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"moduleKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"moduleKey"}}},{"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":"tags"}},{"kind":"Field","name":{"kind":"Name","value":"domains"}},{"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":"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 GetProgramAdminsAndModulesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProgramAdminsAndModules"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"moduleKey"}},"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":"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"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"getModule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"moduleKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"moduleKey"}}},{"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":"tags"}},{"kind":"Field","name":{"kind":"Name","value":"labels"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"projectName"}},{"kind":"Field","name":{"kind":"Name","value":"domains"}},{"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":"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"}}]}},{"kind":"Field","name":{"kind":"Name","value":"recentPullRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"mergedAt"}},{"kind":"Field","name":{"kind":"Name","value":"organizationName"}},{"kind":"Field","name":{"kind":"Name","value":"repositoryName"}},{"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"}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const GetModuleIssuesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModuleIssues"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"moduleKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"20"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"label"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"moduleKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"moduleKey"}}},{"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"issuesCount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"label"},"value":{"kind":"Variable","name":{"kind":"Name","value":"label"}}}]},{"kind":"Field","name":{"kind":"Name","value":"availableLabels"}},{"kind":"Field","name":{"kind":"Name","value":"issues"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"label"},"value":{"kind":"Variable","name":{"kind":"Name","value":"label"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"isMerged"}},{"kind":"Field","name":{"kind":"Name","value":"labels"}},{"kind":"Field","name":{"kind":"Name","value":"assignees"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const GetModuleIssuesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModuleIssues"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"programKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"moduleKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"20"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"label"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModule"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"moduleKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"moduleKey"}}},{"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"issuesCount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"label"},"value":{"kind":"Variable","name":{"kind":"Name","value":"label"}}}]},{"kind":"Field","name":{"kind":"Name","value":"availableLabels"}},{"kind":"Field","name":{"kind":"Name","value":"issues"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"label"},"value":{"kind":"Variable","name":{"kind":"Name","value":"label"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"isMerged"}},{"kind":"Field","name":{"kind":"Name","value":"labels"}},{"kind":"Field","name":{"kind":"Name","value":"taskDeadline"}},{"kind":"Field","name":{"kind":"Name","value":"assignees"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file
+ Deadline +
+ + {status.text} + +