diff --git a/src/ui/components/cards.py b/src/ui/components/cards.py index c5b200f..07e3ff8 100644 --- a/src/ui/components/cards.py +++ b/src/ui/components/cards.py @@ -1,29 +1,171 @@ -from textual.widgets import Static, Label -from textual.containers import Container -from typing import List, Optional +"""Card components for DevDash.""" +from textual.widgets import Static + +from src.models import IssueModel, PullRequestModel, WorkflowModel -class PullRequestCard(Container): - def __init__( - self, - title: str, - author: str, - number: int, - draft: bool = False, - labels: List[str] = None, - **kwargs - ): - super().__init__(**kwargs) - self.title = title - self.author = author - self.number = number - self.draft = draft - self.labels = labels or [] +class PullRequestCard(Static): + """Pull request card component.""" - def compose(self) -> ComposeResult: - yield Label(f"PR #{self.number}: {self.title}", classes="pr-title") - yield Static(f"by {self.author}", classes="pr-author") - if self.labels: - yield Static(f"Labels: {', '.join(self.labels)}", classes="pr-labels") - if self.draft: - yield Static("[DRAFT]", classes="draft-badge") + CSS = """ + PullRequestCard { + height: auto; + border: solid $surface; + padding: 1; + margin: 0 1 1 0; + } + """ + + def __init__(self, pr: PullRequestModel): + """Initialize PR card. + + Args: + pr: Pull request model. + """ + super().__init__() + self.pr = pr + + def compose(self): + """Compose the card.""" + yield Static(self._get_content()) + + def _get_content(self) -> str: + """Get card content. + + Returns: + Formatted card content. + """ + pr = self.pr + lines = [] + lines.append(f"#{pr.number} [link]{pr.title[:40]}[/]") + lines.append(f"[dim]by[/] {pr.author}") + + if pr.checks_total > 0: + if pr.checks_passed is True: + checks_status = "[green]✓[/]" + elif pr.checks_passed is False: + checks_status = "[red]✗[/]" + else: + checks_status = "[yellow]?[/]" + lines.append(f"[dim]checks:[/] {checks_status} {pr.checks_success}/{pr.checks_total}") + + if pr.labels: + label_text = ", ".join(f"[cyan]{label}[/]" for label in pr.labels[:3]) + if len(pr.labels) > 3: + label_text += f" [dim]+{len(pr.labels) - 3}[/]" + lines.append(label_text) + + return "\n".join(lines) + + +class IssueCard(Static): + """Issue card component.""" + + CSS = """ + IssueCard { + height: auto; + border: solid $surface; + padding: 1; + margin: 0 1 1 0; + } + """ + + def __init__(self, issue: IssueModel): + """Initialize issue card. + + Args: + issue: Issue model. + """ + super().__init__() + self.issue = issue + + def compose(self): + """Compose the card.""" + yield Static(self._get_content()) + + def _get_content(self) -> str: + """Get card content. + + Returns: + Formatted card content. + """ + issue = self.issue + lines = [] + lines.append(f"#{issue.number} [link]{issue.title[:40]}[/]") + lines.append(f"[dim]by[/] {issue.author}") + + if issue.labels: + label_text = ", ".join(f"[cyan]{label}[/]" for label in issue.labels[:3]) + if len(issue.labels) > 3: + label_text += f" [dim]+{len(issue.labels) - 3}[/]" + lines.append(label_text) + + return "\n".join(lines) + + +class WorkflowCard(Static): + """Workflow card component.""" + + CSS = """ + WorkflowCard { + height: auto; + border: solid $surface; + padding: 1; + margin: 0 1 1 0; + } + """ + + def __init__(self, workflow: WorkflowModel): + """Initialize workflow card. + + Args: + workflow: Workflow model. + """ + super().__init__() + self.workflow = workflow + + def compose(self): + """Compose the card.""" + yield Static(self._get_content()) + + def _get_content(self) -> str: + """Get card content. + + Returns: + Formatted card content. + """ + wf = self.workflow + status_colors = { + "queued": "yellow", + "in_progress": "blue", + "completed": "green" + } + conclusion_colors = { + "success": "green", + "failure": "red", + "cancelled": "yellow", + "skipped": "gray" + } + + color = status_colors.get(wf.status, "gray") + if wf.status == "completed" and wf.conclusion: + color = conclusion_colors.get(wf.conclusion, "gray") + + icon = "○" if wf.status == "queued" else "◉" if wf.status == "in_progress" else "✓" if wf.conclusion == "success" else "✗" if wf.conclusion == "failure" else "○" + + lines = [] + lines.append(f"[{color}]{icon}[/] {wf.name} #{wf.run_number}") + lines.append(f"[dim]branch:[/] {wf.head_branch[:20]}") + lines.append(f"[dim]commit:[/] {wf.head_sha[:7]}") + + if wf.duration_seconds: + duration = wf.duration_seconds + if duration < 60: + duration_text = f"{duration}s" + elif duration < 3600: + duration_text = f"{duration // 60}m {duration % 60}s" + else: + duration_text = f"{duration // 3600}h {(duration % 3600) // 60}m" + lines.append(f"[dim]duration:[/] {duration_text}") + + return "\n".join(lines)