-
Notifications
You must be signed in to change notification settings - Fork 2
Feat/issue overview #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9afe0c0
6719902
a83401e
9732fb8
f4d6b93
93ebabe
c5ead63
d2f85ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| <script setup lang="ts"> | ||
| const { t } = useI18n() | ||
| const repoStore = useRepositoryStore() | ||
| const issueStore = useIssueStore() | ||
| const { settings, update } = useUserSettings() | ||
|
|
||
| const open = ref(false) | ||
|
|
||
| const reposWithCounts = computed(() => | ||
| repoStore.repos | ||
| .filter(r => !r.archived) | ||
| .map(r => ({ | ||
| ...r, | ||
| openIssues: repoStore.issueCounts[r.fullName] ?? 0, | ||
| openPrs: repoStore.prCounts[r.fullName] ?? 0, | ||
| })) | ||
| .sort((a, b) => b.openIssues - a.openIssues), | ||
| ) | ||
|
|
||
| const selectedRepoData = computed(() => | ||
| reposWithCounts.value.find(r => r.fullName === issueStore.selectedRepo), | ||
| ) | ||
|
|
||
| async function select(fullName: string) { | ||
| open.value = false | ||
| await issueStore.selectRepo(fullName) | ||
| update({ selectedRepo: fullName }) | ||
| } | ||
|
|
||
| // Restore from settings on mount | ||
| onMounted(async () => { | ||
| await repoStore.fetchAll() | ||
| const saved = settings.value?.selectedRepo | ||
| if (saved && repoStore.repos.some(r => r.fullName === saved)) { | ||
| await issueStore.selectRepo(saved) | ||
| } | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <UPopover | ||
| v-model:open="open" | ||
| :content="{ side: 'bottom', align: 'start' }" | ||
| > | ||
| <UButton | ||
| variant="outline" | ||
| color="neutral" | ||
| class="w-full justify-between" | ||
| trailing-icon="i-lucide-chevrons-up-down" | ||
| > | ||
| <template v-if="selectedRepoData"> | ||
| <div class="truncate flex justify-center font-medium"> | ||
| <span>{{ selectedRepoData.name }}</span> | ||
|
|
||
| <UBadge | ||
| v-if="selectedRepoData.openIssues" | ||
| color="error" | ||
| variant="subtle" | ||
| size="xs" | ||
| class="ml-2 gap-1" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-circle-dot" | ||
| class="size-3" | ||
| /> | ||
| {{ selectedRepoData.openIssues }} | ||
| </UBadge> | ||
| </div> | ||
| </template> | ||
| <span | ||
| v-else | ||
| class="text-muted" | ||
| >{{ t('issues.selectRepo') }}</span> | ||
| </UButton> | ||
|
|
||
| <template #content> | ||
| <div class="w-96 overflow-y-auto max-h-96"> | ||
| <button | ||
| v-for="repo in reposWithCounts" | ||
| :key="repo.id" | ||
| class="w-full text-left px-3 py-2.5 hover:bg-elevated transition-colors flex items-start gap-3 cursor-pointer" | ||
| :class="{ 'bg-elevated/50': repo.fullName === issueStore.selectedRepo }" | ||
| @click="select(repo.fullName)" | ||
| > | ||
| <UIcon | ||
| :name="repo.fullName === issueStore.selectedRepo ? 'i-lucide-check' : 'i-lucide-book-marked'" | ||
| class="size-4 mt-0.5 shrink-0" | ||
| :class="repo.fullName === issueStore.selectedRepo ? 'text-primary' : 'text-muted'" | ||
| /> | ||
| <div class="min-w-0 flex-1"> | ||
| <div class="flex items-center gap-2"> | ||
| <span class="text-sm font-medium truncate">{{ repo.name }}</span> | ||
| <UBadge | ||
| v-if="repo.visibility === 'private'" | ||
| color="neutral" | ||
| variant="subtle" | ||
| size="xs" | ||
| > | ||
| {{ t('repos.badge.private') }} | ||
| </UBadge> | ||
| </div> | ||
| <p | ||
| v-if="repo.description" | ||
| class="text-xs text-muted truncate mt-0.5" | ||
| > | ||
| {{ repo.description }} | ||
| </p> | ||
| <div class="flex items-center gap-3 mt-1"> | ||
| <span | ||
| v-if="repo.openIssues" | ||
| class="inline-flex items-center gap-1 text-xs text-rose-500" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-circle-dot" | ||
| class="size-3.5" | ||
| /> | ||
| {{ t('issues.openCount', { count: repo.openIssues }) }} | ||
| </span> | ||
| <span | ||
| v-if="repo.openPrs" | ||
| class="inline-flex items-center gap-1 text-xs text-blue-500" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-git-pull-request" | ||
| class="size-3.5" | ||
| /> | ||
| {{ repo.openPrs }} | ||
| </span> | ||
| <span | ||
| v-if="repo.language" | ||
| class="text-xs text-dimmed" | ||
| > | ||
| {{ repo.language }} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </button> | ||
|
|
||
| <p | ||
| v-if="!reposWithCounts.length" | ||
| class="px-3 py-4 text-sm text-muted text-center" | ||
| > | ||
| {{ t('repos.noResults') }} | ||
| </p> | ||
| </div> | ||
| </template> | ||
| </UPopover> | ||
| </template> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| <script setup lang="ts"> | ||
| import type { Issue } from '~~/shared/types/issue' | ||
|
|
||
| const props = defineProps<{ | ||
| issue: Issue | ||
| }>() | ||
|
|
||
| const { t } = useI18n() | ||
| const createdAgo = useTimeAgo(computed(() => props.issue.createdAt)) | ||
| const updatedAgo = useTimeAgo(computed(() => props.issue.updatedAt)) | ||
|
|
||
| const stateIcon = computed(() => { | ||
| if (props.issue.state === 'OPEN') return 'i-lucide-circle-dot' | ||
| if (props.issue.stateReason === 'NOT_PLANNED') return 'i-lucide-circle-slash' | ||
| return 'i-lucide-check-circle' | ||
| }) | ||
|
|
||
| const stateColor = computed(() => { | ||
| if (props.issue.state === 'OPEN') return 'text-emerald-500' | ||
| if (props.issue.stateReason === 'NOT_PLANNED') return 'text-neutral-400' | ||
| return 'text-violet-500' | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <a | ||
| :href="issue.url" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| class="flex items-start gap-3 px-4 py-3 hover:bg-elevated transition-colors" | ||
| > | ||
| <!-- State icon --> | ||
| <UIcon | ||
| :name="stateIcon" | ||
| class="size-5 mt-0.5 shrink-0" | ||
| :class="stateColor" | ||
| /> | ||
|
|
||
| <!-- Content --> | ||
| <div class="min-w-0 flex-1"> | ||
| <!-- Row 1: Title + labels --> | ||
| <div class="flex items-center gap-2 flex-wrap"> | ||
| <span class="font-medium text-highlighted hover:underline"> | ||
| {{ issue.title }} | ||
| </span> | ||
| <UBadge | ||
| v-for="label in issue.labels" | ||
| :key="label.name" | ||
| variant="subtle" | ||
| size="xs" | ||
| :style="{ backgroundColor: `#${label.color}20`, color: `#${label.color}` }" | ||
| > | ||
| {{ label.name }} | ||
| </UBadge> | ||
| </div> | ||
|
|
||
| <!-- Row 2: Meta --> | ||
| <div class="flex items-center gap-3 mt-1 text-xs text-muted"> | ||
| <UTooltip | ||
| v-if="!issue.maintainerCommented && issue.commentCount > 0" | ||
| :text="t('issues.needsResponse')" | ||
| > | ||
| <span class="inline-flex items-center gap-0.5 text-amber-500"> | ||
| <UIcon | ||
| name="i-lucide-message-circle-warning" | ||
| class="size-3.5" | ||
| /> | ||
| </span> | ||
| </UTooltip> | ||
| <span class="inline-flex items-center gap-1"> | ||
| <UAvatar | ||
| :src="issue.author.avatarUrl" | ||
| :alt="issue.author.login" | ||
| size="3xs" | ||
| /> | ||
| {{ issue.author.login }} | ||
| </span> | ||
| <span>#{{ issue.number }}</span> | ||
| <span>{{ createdAgo }}</span> | ||
| <span>{{ updatedAgo }}</span> | ||
|
|
||
| <span | ||
| v-if="issue.commentCount" | ||
| class="inline-flex items-center gap-0.5" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-message-square" | ||
| class="size-3.5" | ||
| /> | ||
| {{ issue.commentCount }} | ||
| </span> | ||
|
|
||
| <span | ||
| v-if="issue.linkedPrCount" | ||
| class="inline-flex items-center gap-0.5 text-blue-500" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-git-pull-request" | ||
| class="size-3.5" | ||
| /> | ||
| {{ issue.linkedPrCount }} | ||
| </span> | ||
|
|
||
| <span | ||
| v-if="issue.state === 'CLOSED'" | ||
| class="inline-flex items-center gap-0.5" | ||
| :class="issue.stateReason === 'NOT_PLANNED' ? 'text-neutral-400' : 'text-violet-500'" | ||
| > | ||
| {{ issue.stateReason === 'NOT_PLANNED' ? t('issues.closedAsNotPlanned') : t('issues.closedAs') }} | ||
| </span> | ||
|
|
||
| <span | ||
| v-if="issue.milestone" | ||
| class="inline-flex items-center gap-0.5" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-milestone" | ||
| class="size-3.5" | ||
| /> | ||
| {{ issue.milestone }} | ||
| </span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Right side: Assignees + author --> | ||
| <div class="flex items-center gap-1 shrink-0"> | ||
| <UTooltip | ||
| v-for="assignee in issue.assignees" | ||
| :key="assignee.login" | ||
| :text="assignee.login" | ||
| > | ||
| <UAvatar | ||
| :src="assignee.avatarUrl" | ||
| :alt="assignee.login" | ||
| size="xs" | ||
| /> | ||
| </UTooltip> | ||
| </div> | ||
| </a> | ||
|
Comment on lines
+8
to
+139
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing required row metadata (repo, author, created time). PR objectives call for repository name, author avatar/name, and relative created time in each row. Currently only updated time and assignees are rendered, so the overview can’t disambiguate cross‑repo issues or show author/created info. 💡 Minimal sketch of the missing metadata wiring<script setup lang="ts">
@@
const { t } = useI18n()
const updatedAgo = useTimeAgo(computed(() => props.issue.updatedAt))
+const createdAgo = useTimeAgo(computed(() => props.issue.createdAt))
@@
</script><!-- Row 2: Meta -->
<div class="flex items-center gap-3 mt-1 text-xs text-muted">
+ <span class="inline-flex items-center gap-1">
+ <UIcon name="i-lucide-book" class="size-3.5" />
+ {{ issue.repository.nameWithOwner }}
+ </span>
+ <span class="inline-flex items-center gap-1">
+ <UAvatar :src="issue.author.avatarUrl" :alt="issue.author.login" size="xs" />
+ {{ issue.author.login }}
+ </span>
<span>#{{ issue.number }}</span>
+ <span>{{ createdAgo }}</span>
<span>{{ updatedAgo }}</span>
...
</div>🤖 Prompt for AI Agents |
||
| </template> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| <script setup lang="ts"> | ||
| const { t } = useI18n() | ||
| const store = useIssueStore() | ||
|
|
||
| function toggleFilter(key: string) { | ||
| const current = store.activeFilters | ||
| if (current.includes(key)) { | ||
| store.activeFilters = current.filter(f => f !== key) | ||
| } | ||
| else { | ||
| store.activeFilters = [...current, key] | ||
| } | ||
| } | ||
|
|
||
| const filterChips = computed(() => [ | ||
| { key: 'unassigned', label: t('issues.filter.unassigned'), icon: 'i-lucide-user-x' }, | ||
| { key: 'hasLinkedPr', label: t('issues.filter.hasLinkedPr'), icon: 'i-lucide-git-pull-request', color: 'text-blue-500' }, | ||
| { key: 'noLinkedPr', label: t('issues.filter.noLinkedPr'), icon: 'i-lucide-git-pull-request-closed', color: 'text-rose-500' }, | ||
| { key: 'hasMilestone', label: t('issues.filter.hasMilestone'), icon: 'i-lucide-milestone' }, | ||
| ]) | ||
|
|
||
| const sortOptions = computed(() => [ | ||
| { label: t('issues.sort.critical'), value: 'critical' }, | ||
| { label: t('issues.sort.newest'), value: 'newest' }, | ||
| { label: t('issues.sort.oldest'), value: 'oldest' }, | ||
| { label: t('issues.sort.mostCommented'), value: 'mostCommented' }, | ||
| { label: t('issues.sort.leastCommented'), value: 'leastCommented' }, | ||
| { label: t('issues.sort.recentlyUpdated'), value: 'recentlyUpdated' }, | ||
| ]) | ||
| </script> | ||
|
|
||
| <template> | ||
| <div class="flex flex-col gap-3"> | ||
| <!-- Row 1: Search + count --> | ||
| <div class="flex items-center gap-3"> | ||
| <UInput | ||
| v-model="store.search" | ||
| :placeholder="t('issues.search')" | ||
| icon="i-lucide-search" | ||
| class="flex-1" | ||
| /> | ||
| <span class="text-sm text-muted shrink-0"> | ||
| {{ t('issues.count', store.filteredIssues.length) }} | ||
| </span> | ||
| </div> | ||
|
|
||
| <!-- Row 2: Filter chips + sort --> | ||
| <div class="flex items-center gap-2 flex-wrap"> | ||
| <button | ||
| v-for="chip in filterChips" | ||
| :key="chip.key" | ||
| class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium transition-colors cursor-pointer" | ||
| :class="store.activeFilters.includes(chip.key) | ||
| ? (chip.color ? `bg-muted ${chip.color} ring-1 ring-current/20` : 'bg-primary text-inverted') | ||
| : 'bg-muted text-toned hover:bg-accented'" | ||
| @click="toggleFilter(chip.key)" | ||
| > | ||
| <UIcon | ||
| :name="chip.icon" | ||
| class="size-3.5" | ||
| /> | ||
| {{ chip.label }} | ||
| </button> | ||
|
|
||
| <div class="ml-auto"> | ||
| <USelect | ||
| v-model="store.sortKey" | ||
| :items="sortOptions" | ||
| size="xs" | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Row 3: Label chips --> | ||
| <div | ||
| v-if="store.availableLabels.length" | ||
| class="flex items-center gap-1.5 flex-wrap" | ||
| > | ||
| <button | ||
| v-for="label in store.availableLabels" | ||
| :key="label" | ||
| class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md text-xs transition-colors cursor-pointer" | ||
| :class="store.activeFilters.includes(`label:${label}`) | ||
| ? 'bg-primary text-inverted' | ||
| : 'bg-muted text-toned hover:bg-accented'" | ||
| @click="toggleFilter(`label:${label}`)" | ||
| > | ||
| {{ label }} | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </template> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repository name still missing from meta row.
PR objectives require displaying "title + number + repository name" to disambiguate issues across repositories. The author and created time from the prior feedback have been added, but the repository identifier is still absent.
🔧 Proposed fix to add repository name
<!-- Row 2: Meta --> <div class="flex items-center gap-3 mt-1 text-xs text-muted"> + <span class="inline-flex items-center gap-1"> + <UIcon name="i-lucide-book-marked" class="size-3.5" /> + {{ issue.repository }} + </span> <UTooltip v-if="!issue.maintainerCommented && issue.commentCount > 0" :text="t('issues.needsResponse')"🤖 Prompt for AI Agents