Menu

Change language

GitHub repo

Exit Interview Survey

blockhr

Structured employee offboarding survey with department sentiment analysis, departure drivers, 5-star experience ratings, qualitative feedback textareas, and a knowledge handover & asset return checklist.

Installation

$npx uipkge-ng@latest add exit-interview-survey
Manifest:https://uipkge.dev/r/angular/exit-interview-survey.jsonInstalls to:src/app/components/blocks/exit-interview-survey/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

DepartureReason
interface DepartureReason {
  value: string
  label: string
  description: string
  icon: LucideIconData
}
RatingDimension
interface RatingDimension {
  id: RatingDimensionId
  title: string
  description: string
  category: string
}
ReturnOption
interface ReturnOption {
  value: string
  label: string
  description: string
  badge: string
  badgeVariant: BadgeVariant
}

Files installed (2)

  • src/app/components/blocks/exit-interview-survey/exit-interview-survey.component.ts31.5 kB
    // Structured employee offboarding survey with department sentiment analysis, departure
    // drivers, 5-star experience ratings, qualitative feedback textareas, and a knowledge
    // handover & asset return checklist. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import {
      Award,
      Building2,
      Calendar,
      CheckCircle2,
      ClipboardCheck,
      Compass,
      FileCheck,
      FileCode2,
      HeartHandshake,
      KeyRound,
      Laptop,
      Lock,
      LucideAngularModule,
      MessageSquareQuote,
      Rocket,
      RotateCcw,
      Send,
      ShieldCheck,
      Star,
      TrendingUp,
      UserCheck,
      type LucideIconData,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent, type BadgeVariant } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardFooterComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiCheckboxComponent } from '@/ui/checkbox/checkbox.component'
    import { UiLabelComponent } from '@/ui/label/label.component'
    import { UiProgressComponent } from '@/ui/progress/progress.component'
    import { UiRadioGroupComponent, UiRadioGroupItemComponent } from '@/ui/radio-group/radio-group.component'
    import { UiRatingComponent } from '@/ui/rating/rating.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import { UiTextareaComponent } from '@/ui/textarea/textarea.component'
    
    interface DepartureReason {
      value: string
      label: string
      description: string
      icon: LucideIconData
    }
    
    const departureReasons: DepartureReason[] = [
      {
        value: 'career-growth',
        label: 'Career Growth & Promotion',
        description: 'Seeking broader technical leadership scope, faster progression, or new architectural challenges.',
        icon: TrendingUp,
      },
      {
        value: 'compensation',
        label: 'Compensation / Total Rewards',
        description: 'Competitive market offers, higher base salary band, equity incentives, or bonus structures.',
        icon: Award,
      },
      {
        value: 'relocation',
        label: 'Relocation & Remote Freedom',
        description: 'Geographic move, personal relocation, or seeking broader international and async flexibility.',
        icon: Building2,
      },
      {
        value: 'work-life',
        label: 'Work-Life Balance',
        description: 'Need for sustainable working cadence, reduced on-call intensity, and dedicated personal time.',
        icon: HeartHandshake,
      },
      {
        value: 'leadership',
        label: 'Leadership & Direction',
        description: 'Strategic alignment with executive roadmap, product vision, or communication cadence.',
        icon: Compass,
      },
      {
        value: 'venture',
        label: 'Starting Own Venture',
        description: 'Launching an independent technical startup, consulting practice, or entrepreneurial venture.',
        icon: Rocket,
      },
    ]
    
    export type RatingDimensionId = 'management' | 'collaboration' | 'compensation' | 'culture' | 'flexibility'
    
    interface RatingDimension {
      id: RatingDimensionId
      title: string
      description: string
      category: string
    }
    
    const ratingDimensions: RatingDimension[] = [
      {
        id: 'management',
        title: 'Management & Leadership',
        description: 'Direct manager support, transparent 1:1 mentorship, goal clarity, and career advocacy.',
        category: 'Leadership',
      },
      {
        id: 'collaboration',
        title: 'Team Collaboration',
        description: 'Cross-functional synergy, peer code review quality, mutual trust, and psychological safety.',
        category: 'Team Dynamics',
      },
      {
        id: 'compensation',
        title: 'Compensation & Benefits',
        description: 'Base salary competitiveness, equity appreciation, health coverage, and wellness allowances.',
        category: 'Total Rewards',
      },
      {
        id: 'culture',
        title: 'Engineering Culture',
        description: 'Architectural standards, modern tooling, automated testing, CI/CD speed, and technical autonomy.',
        category: 'Craft & Standards',
      },
      {
        id: 'flexibility',
        title: 'Work-Life Flexibility',
        description: 'Async-first communication, sustainable sprint planning, and reasonable on-call expectations.',
        category: 'Well-being',
      },
    ]
    
    interface ReturnOption {
      value: string
      label: string
      description: string
      badge: string
      badgeVariant: BadgeVariant
    }
    
    const returnOptions: ReturnOption[] = [
      {
        value: 'yes',
        label: 'Yes, definitely open',
        description: 'Strongly open to returning for future leadership or staff-level architectural initiatives.',
        badge: 'Alumni Priority',
        badgeVariant: 'success',
      },
      {
        value: 'maybe',
        label: 'Maybe, under right conditions',
        description: 'Would consider returning under different organizational structure or roadmap scope.',
        badge: 'Conditionally Open',
        badgeVariant: 'secondary',
      },
      {
        value: 'no',
        label: 'No, pursuing new pathways',
        description: 'Focused on long-term career growth in independent ventures or other sectors.',
        badge: 'New Trajectory',
        badgeVariant: 'outline',
      },
    ]
    
    export type HandoverKey = 'codebase' | 'architecture' | 'credentials' | 'equipment'
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-exit-interview-survey, [ui-exit-interview-survey]',
      standalone: true,
      // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiAvatarImageComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardFooterComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiCheckboxComponent,
        UiLabelComponent,
        UiProgressComponent,
        UiRadioGroupComponent,
        UiRadioGroupItemComponent,
        UiRatingComponent,
        UiSeparatorComponent,
        UiTextareaComponent,
      ],
      template: `
        <div data-slot="exit-interview-survey" [class]="rootClass">
          <!-- Header & Employee Context Card -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-content class="p-6">
              <div class="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
                <div class="flex flex-col gap-4 sm:flex-row sm:items-center">
                  <ui-avatar class="border-border size-16 shrink-0 border sm:size-20">
                    <ui-avatar-image
                      src="https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&auto=format&fit=crop&q=80"
                      alt="Elena Rostova"
                    />
                    <ui-avatar-fallback>ER</ui-avatar-fallback>
                  </ui-avatar>
                  <div class="space-y-1.5">
                    <div class="flex flex-wrap items-center gap-2">
                      <h1 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">
                        Employee Exit Survey & Offboarding Review
                      </h1>
                    </div>
                    <p class="text-muted-foreground text-sm font-medium">
                      Elena Rostova · Senior Staff Engineer · 3.5 Years Tenure
                    </p>
                    <div class="flex flex-wrap items-center gap-2 pt-1 text-xs">
                      <span ui-badge variant="secondary" class="font-medium"> Engineering · Core Platform </span>
                      <span ui-badge variant="outline" class="gap-1 text-xs font-normal">
                        <lucide-icon [img]="Calendar" class="size-3" />
                        Final Day: Sep 30, 2026
                      </span>
                      <span class="text-muted-foreground">
                        Manager: <strong class="text-foreground font-medium">Marcus Vance</strong>
                      </span>
                    </div>
                  </div>
                </div>
    
                <div class="flex flex-wrap items-center gap-3 sm:self-start lg:self-center">
                  @if (!isSubmitted()) {
                    <button
                      ui-button
                      size="sm"
                      class="bg-primary text-primary-foreground gap-2 shadow-xs"
                      (click)="handleSubmit()"
                    >
                      <lucide-icon [img]="Send" class="size-4" />
                      Submit Survey
                    </button>
                  } @else {
                    <button ui-button variant="outline" size="sm" class="gap-2 shadow-xs" (click)="handleReset()">
                      <lucide-icon [img]="RotateCcw" class="size-4" />
                      Edit Survey Answers
                    </button>
                  }
                </div>
              </div>
    
              <div class="border-border/80 bg-muted/40 mt-6 flex items-start gap-3 rounded-lg border p-3.5 text-xs">
                <lucide-icon [img]="ShieldCheck" class="text-primary mt-0.5 size-4 shrink-0" />
                <div class="space-y-0.5">
                  <p class="text-foreground font-semibold">Strictly Confidential People Ops Review</p>
                  <p class="text-muted-foreground leading-relaxed">
                    Your candid responses are confidential and used exclusively by People Ops to improve company culture,
                    leadership effectiveness, and engineering workflows. Summary insights are aggregated and anonymized.
                  </p>
                </div>
              </div>
            </div>
          </div>
    
          @if (isSubmitted()) {
            <div
              class="border-success/30 bg-success/10 text-foreground dark:text-foreground flex items-start gap-3 rounded-xl border p-4 shadow-xs"
            >
              <lucide-icon [img]="CheckCircle2" class="text-success mt-0.5 size-5 shrink-0" />
              <div class="flex-1 space-y-1">
                <div class="flex flex-wrap items-center justify-between gap-2">
                  <p class="text-sm font-semibold">Exit Survey Successfully Submitted</p>
                  <span class="font-mono text-xs font-medium">Receipt #{{ submissionId }}</span>
                </div>
                <p class="text-success text-xs leading-relaxed">
                  Thank you for your dedicated 3.5 years of service and leadership. Your constructive feedback has been
                  securely logged for the People Ops quarterly retention review and alumni network registration.
                </p>
              </div>
            </div>
          }
    
          <!-- Department Sentiment & Handover Scorecard Strip -->
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <div ui-card class="border-border bg-card shadow-xs">
              <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
                <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Overall Experience Score</h3>
                <lucide-icon [img]="Star" class="text-warning size-4" />
              </div>
              <div ui-card-content class="space-y-2">
                <div class="flex items-baseline gap-2">
                  <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">
                    {{ averageRating() }}
                  </span>
                  <span class="text-muted-foreground text-xs font-medium">/ 5.0</span>
                </div>
                <div class="space-y-1">
                  <div ui-progress [value]="sentimentPercentage()" class="h-1.5 w-full"></div>
                  <div class="flex items-center justify-between text-xs">
                    <span class="text-success font-medium">+0.4 vs Dept Avg</span>
                    <span class="text-muted-foreground font-mono tabular-nums">{{ sentimentPercentage() }}%</span>
                  </div>
                </div>
              </div>
            </div>
    
            <div ui-card class="border-border bg-card shadow-xs">
              <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
                <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Primary Departure Driver</h3>
                <lucide-icon [img]="Compass" class="text-primary size-4" />
              </div>
              <div ui-card-content class="space-y-1.5">
                <div class="text-foreground text-base font-bold tracking-tight sm:text-lg">Career Growth</div>
                <p class="text-muted-foreground text-xs leading-relaxed">
                  Targeting Principal/Architect roles in cloud infrastructure.
                </p>
              </div>
            </div>
    
            <div ui-card class="border-border bg-card shadow-xs">
              <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
                <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Knowledge Handover</h3>
                <lucide-icon [img]="ClipboardCheck" class="text-primary size-4" />
              </div>
              <div ui-card-content class="space-y-2">
                <div class="flex items-baseline gap-2">
                  <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">
                    {{ handoverPercentage() }}%
                  </span>
                  <span class="text-muted-foreground text-xs font-medium">({{ handoverCompletedCount() }} of 4 tasks)</span>
                </div>
                <div class="space-y-1">
                  <div ui-progress [value]="handoverPercentage()" class="h-1.5 w-full"></div>
                  <p class="text-muted-foreground text-xs">IT asset return pending</p>
                </div>
              </div>
            </div>
    
            <div ui-card class="border-border bg-card shadow-xs">
              <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
                <h3 ui-card-title class="text-muted-foreground text-xs font-medium">Alumni Status</h3>
                <lucide-icon [img]="UserCheck" class="text-success size-4" />
              </div>
              <div ui-card-content class="space-y-1.5">
                <div class="flex items-center gap-1.5">
                  <span ui-badge variant="success" class="text-xs font-semibold">Eligible for Re-Hire</span>
                </div>
                <p class="text-muted-foreground text-xs leading-relaxed">
                  Registered for Alumni Leadership Network & fast-track referrals.
                </p>
              </div>
            </div>
          </div>
    
          <!-- SECTION 1: Primary Reason for Departure -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <div class="flex items-center gap-2">
                <div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md">
                  <lucide-icon [img]="Compass" class="size-4" />
                </div>
                <div>
                  <h3 ui-card-title class="text-base font-semibold">1. Primary Reason for Departure</h3>
                  <p ui-card-description class="text-xs">
                    Select the primary factor that most heavily influenced your decision to pursue new opportunities.
                  </p>
                </div>
              </div>
            </div>
    
            <div ui-card-content class="space-y-4">
              <ui-radio-group
                [value]="primaryReason()"
                (valueChange)="primaryReason.set($event)"
                class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3"
              >
                @for (item of reasons; track item.value) {
                  <label
                    [attr.for]="'reason-' + item.value"
                    [class]="optionCardClass(primaryReason() === item.value)"
                  >
                    <ui-radio-group-item [id]="'reason-' + item.value" [value]="item.value" class="mt-0.5" />
                    <div class="space-y-1">
                      <div class="flex items-center gap-1.5">
                        <span class="text-foreground text-xs leading-none font-semibold">{{ item.label }}</span>
                      </div>
                      <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p>
                    </div>
                  </label>
                }
              </ui-radio-group>
    
              <div class="space-y-2 pt-2">
                <label ui-label for="departure-notes" class="text-xs font-medium">
                  Additional Context on Your Departure Decision (Optional)
                </label>
                <ui-textarea
                  id="departure-notes"
                  [value]="departureNotes()"
                  (valueChange)="departureNotes.set($event)"
                  [rows]="2"
                  placeholder="Share specific context regarding your career progression, next steps, or decision drivers..."
                  class="text-xs"
                />
              </div>
            </div>
          </div>
    
          <!-- SECTION 2: Experience & Culture Ratings -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div class="flex items-center gap-2">
                  <div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md">
                    <lucide-icon [img]="Star" class="size-4" />
                  </div>
                  <div>
                    <h3 ui-card-title class="text-base font-semibold">2. Experience & Culture Ratings</h3>
                    <p ui-card-description class="text-xs">
                      Rate each dimension of your experience on a scale of 1 (Needs Serious Improvement) to 5 (Outstanding).
                    </p>
                  </div>
                </div>
                <span ui-badge variant="outline" class="w-fit text-xs font-medium tabular-nums">
                  Average: {{ averageRating() }} / 5.0
                </span>
              </div>
            </div>
    
            <div ui-card-content class="space-y-4">
              <div class="divide-border divide-y rounded-lg border">
                @for (dim of dimensions; track dim.id) {
                  <div
                    class="hover:bg-muted/20 flex flex-col gap-3 p-4 transition-colors sm:flex-row sm:items-center sm:justify-between"
                  >
                    <div class="space-y-1">
                      <div class="flex items-center gap-2">
                        <span class="text-foreground text-sm font-semibold">{{ dim.title }}</span>
                        <span ui-badge variant="secondary" class="text-xs font-normal">{{ dim.category }}</span>
                      </div>
                      <p class="text-muted-foreground max-w-xl text-xs leading-relaxed">{{ dim.description }}</p>
                    </div>
                    <div class="flex shrink-0 items-center gap-3 sm:self-center">
                      <ui-rating
                        [value]="ratingValue(dim.id)"
                        (valueChange)="setRating(dim.id, $event)"
                        [max]="5"
                        density="comfortable"
                        size="small"
                      />
                      <span class="text-foreground min-w-8 text-right font-mono text-xs font-semibold tabular-nums">
                        {{ ratingValue(dim.id) }} / 5
                      </span>
                    </div>
                  </div>
                }
              </div>
            </div>
          </div>
    
          <!-- SECTION 3: Qualitative In-Depth Feedback -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <div class="flex items-center gap-2">
                <div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md">
                  <lucide-icon [img]="MessageSquareQuote" class="size-4" />
                </div>
                <div>
                  <h3 ui-card-title class="text-base font-semibold">3. Qualitative In-Depth Feedback</h3>
                  <p ui-card-description class="text-xs">
                    Detailed reflections to guide organizational enhancements and executive leadership planning.
                  </p>
                </div>
              </div>
            </div>
    
            <div ui-card-content class="space-y-5">
              <div class="space-y-2">
                <label ui-label for="enjoyed-most" class="text-foreground text-xs font-semibold tracking-wide">
                  What did you enjoy most about working at the company?
                </label>
                <ui-textarea
                  id="enjoyed-most"
                  [value]="enjoyedMost()"
                  (valueChange)="enjoyedMost.set($event)"
                  [rows]="3"
                  placeholder="Highlight positive aspects of your team, projects, technical challenges, or company traditions..."
                  class="text-xs leading-relaxed"
                />
              </div>
    
              <ui-separator />
    
              <div class="space-y-2">
                <label ui-label for="to-improve" class="text-foreground text-xs font-semibold tracking-wide">
                  What is one thing the leadership team should immediately improve?
                </label>
                <ui-textarea
                  id="to-improve"
                  [value]="toImprove()"
                  (valueChange)="toImprove.set($event)"
                  [rows]="3"
                  placeholder="Suggest actionable improvements for strategy, tooling, engineering velocity, or team communication..."
                  class="text-xs leading-relaxed"
                />
              </div>
    
              <ui-separator />
    
              <div class="space-y-3">
                <label ui-label class="text-foreground text-xs font-semibold tracking-wide">
                  Would you consider returning to the company in the future?
                </label>
                <ui-radio-group
                  [value]="returnWillingness()"
                  (valueChange)="returnWillingness.set($event)"
                  class="grid grid-cols-1 gap-3 sm:grid-cols-3"
                >
                  @for (opt of returnOpts; track opt.value) {
                    <label
                      [attr.for]="'return-' + opt.value"
                      [class]="optionCardClass(returnWillingness() === opt.value)"
                    >
                      <ui-radio-group-item [id]="'return-' + opt.value" [value]="opt.value" class="mt-0.5" />
                      <div class="space-y-1.5">
                        <div class="flex items-center gap-1.5">
                          <span class="text-foreground text-xs font-semibold">{{ opt.label }}</span>
                        </div>
                        <p class="text-muted-foreground text-xs leading-relaxed">{{ opt.description }}</p>
                        <span ui-badge [variant]="opt.badgeVariant" class="text-xs font-normal">{{ opt.badge }}</span>
                      </div>
                    </label>
                  }
                </ui-radio-group>
              </div>
            </div>
          </div>
    
          <!-- SECTION 4: Knowledge Handover & Asset Return Checklist -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div class="flex items-center gap-2">
                  <div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md">
                    <lucide-icon [img]="ClipboardCheck" class="size-4" />
                  </div>
                  <div>
                    <h3 ui-card-title class="text-base font-semibold">4. Knowledge Handover & Asset Return Checklist</h3>
                    <p ui-card-description class="text-xs">
                      Administrative and technical transition milestones prior to official offboarding date.
                    </p>
                  </div>
                </div>
                <span
                  ui-badge
                  [variant]="handoverCompletedCount() === 4 ? 'success' : 'secondary'"
                  class="w-fit text-xs font-medium tabular-nums"
                >
                  {{ handoverCompletedCount() }} of 4 Completed ({{ handoverPercentage() }}%)
                </span>
              </div>
            </div>
    
            <div ui-card-content class="space-y-4">
              <div class="divide-border divide-y rounded-lg border">
                <div class="hover:bg-muted/20 flex items-start gap-3.5 p-4 transition-colors">
                  <ui-checkbox
                    id="ho-codebase"
                    [checked]="handover().codebase"
                    (checkedChange)="toggleHandover('codebase', $event)"
                    class="mt-0.5"
                  />
                  <div class="space-y-1">
                    <div class="flex items-center gap-2">
                      <lucide-icon [img]="FileCode2" class="text-primary size-4" />
                      <label ui-label for="ho-codebase" class="text-foreground cursor-pointer text-xs font-semibold">
                        Codebase repository ownership transferred
                      </label>
                    </div>
                    <p class="text-muted-foreground text-xs leading-relaxed">
                      Transferred GitHub admin rights, codeowners assignments, and CI/CD secret manager access to incoming
                      lead (Liam Vance).
                    </p>
                  </div>
                </div>
    
                <div class="hover:bg-muted/20 flex items-start gap-3.5 p-4 transition-colors">
                  <ui-checkbox
                    id="ho-architecture"
                    [checked]="handover().architecture"
                    (checkedChange)="toggleHandover('architecture', $event)"
                    class="mt-0.5"
                  />
                  <div class="space-y-1">
                    <div class="flex items-center gap-2">
                      <lucide-icon [img]="FileCheck" class="text-primary size-4" />
                      <label ui-label for="ho-architecture" class="text-foreground cursor-pointer text-xs font-semibold">
                        Architecture documentation updated
                      </label>
                    </div>
                    <p class="text-muted-foreground text-xs leading-relaxed">
                      Core system architecture diagrams, service topology maps, and disaster recovery runbooks completed in
                      Notion & DevPortal.
                    </p>
                  </div>
                </div>
    
                <div class="hover:bg-muted/20 flex items-start gap-3.5 p-4 transition-colors">
                  <ui-checkbox
                    id="ho-credentials"
                    [checked]="handover().credentials"
                    (checkedChange)="toggleHandover('credentials', $event)"
                    class="mt-0.5"
                  />
                  <div class="space-y-1">
                    <div class="flex items-center gap-2">
                      <lucide-icon [img]="KeyRound" class="text-primary size-4" />
                      <label ui-label for="ho-credentials" class="text-foreground cursor-pointer text-xs font-semibold">
                        Passwords & credentials rotated
                      </label>
                    </div>
                    <p class="text-muted-foreground text-xs leading-relaxed">
                      Revoked personal staging SSH keys, rotated AWS IAM secret access tokens, and decommissioned VPN and
                      Vault credentials.
                    </p>
                  </div>
                </div>
    
                <div class="hover:bg-muted/20 flex items-start gap-3.5 p-4 transition-colors">
                  <ui-checkbox
                    id="ho-equipment"
                    [checked]="handover().equipment"
                    (checkedChange)="toggleHandover('equipment', $event)"
                    class="mt-0.5"
                  />
                  <div class="space-y-1">
                    <div class="flex items-center gap-2">
                      <lucide-icon [img]="Laptop" class="text-primary size-4" />
                      <label ui-label for="ho-equipment" class="text-foreground cursor-pointer text-xs font-semibold">
                        Company laptop & security hardware returned
                      </label>
                    </div>
                    <p class="text-muted-foreground text-xs leading-relaxed">
                      MacBook Pro M3 Max, hardware security YubiKey, and company physical access badge received by IT Asset
                      Ops.
                    </p>
                  </div>
                </div>
              </div>
            </div>
    
            <div
              ui-card-footer
              class="border-border/60 bg-muted/20 flex flex-wrap items-center justify-between gap-3 border-t p-4"
            >
              <div class="text-muted-foreground flex items-center gap-2 text-xs">
                <lucide-icon [img]="Lock" class="size-3.5" />
                <span>Requires final sign-off from IT Security & People Operations</span>
              </div>
              @if (!isSubmitted()) {
                <button
                  ui-button
                  size="sm"
                  class="bg-primary text-primary-foreground gap-2 shadow-xs"
                  (click)="handleSubmit()"
                >
                  <lucide-icon [img]="Send" class="size-4" />
                  Submit Final Exit Review
                </button>
              }
            </div>
          </div>
        </div>
      `,
    })
    export class UiExitInterviewSurveyComponent {
      protected readonly Calendar = Calendar
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly ClipboardCheck = ClipboardCheck
      protected readonly Compass = Compass
      protected readonly FileCheck = FileCheck
      protected readonly FileCode2 = FileCode2
      protected readonly KeyRound = KeyRound
      protected readonly Laptop = Laptop
      protected readonly Lock = Lock
      protected readonly MessageSquareQuote = MessageSquareQuote
      protected readonly RotateCcw = RotateCcw
      protected readonly Send = Send
      protected readonly ShieldCheck = ShieldCheck
      protected readonly Star = Star
      protected readonly UserCheck = UserCheck
    
      protected readonly reasons = departureReasons
      protected readonly dimensions = ratingDimensions
      protected readonly returnOpts = returnOptions
      protected readonly submissionId = 'EXIT-2026-9042'
    
      @Input('class') className?: string
      @Input() set initialSubmitted(v: boolean) {
        this.isSubmitted.set(!!v)
      }
    
      readonly primaryReason = signal('career-growth')
      readonly departureNotes = signal(
        'Accepted a Principal Systems Architect role at a Series B infrastructure startup focusing on WebAssembly runtime tooling.',
      )
      readonly ratings = signal<Record<RatingDimensionId, number>>({
        management: 4,
        collaboration: 5,
        compensation: 4,
        culture: 5,
        flexibility: 4,
      })
      readonly enjoyedMost = signal(
        'The engineering team’s exceptional craft and collaborative spirit. Building high-scale distributed platform services alongside humble, brilliant peers was deeply rewarding. Leadership always supported architectural innovation and design system investments.',
      )
      readonly toImprove = signal(
        'Cross-functional roadmap alignment between product management and core platform squads. Late-quarter requirement shifts occasionally created sprint friction and compressed delivery timelines.',
      )
      readonly returnWillingness = signal('yes')
      readonly handover = signal<Record<HandoverKey, boolean>>({
        codebase: true,
        architecture: true,
        credentials: true,
        equipment: false,
      })
      readonly isSubmitted = signal(false)
    
      readonly averageRating = computed(() => {
        const values = Object.values(this.ratings())
        return (values.reduce((acc, val) => acc + val, 0) / values.length).toFixed(1)
      })
      readonly sentimentPercentage = computed(() => Math.round((Number(this.averageRating()) / 5) * 100))
      readonly handoverCompletedCount = computed(() => Object.values(this.handover()).filter(Boolean).length)
      readonly handoverPercentage = computed(() => Math.round((this.handoverCompletedCount() / 4) * 100))
    
      get rootClass(): string {
        return cn('w-full space-y-6', this.className)
      }
    
      optionCardClass(selected: boolean): string {
        return cn(
          'hover:bg-muted/30 flex cursor-pointer items-start gap-3 rounded-lg border p-3.5 transition-colors',
          selected ? 'border-primary/80 bg-primary/5 ring-primary/20 ring-1' : 'border-border bg-card',
        )
      }
    
      ratingValue(id: RatingDimensionId): number {
        return this.ratings()[id]
      }
    
      setRating(id: RatingDimensionId, val: number): void {
        this.ratings.update((prev) => ({ ...prev, [id]: val }))
      }
    
      toggleHandover(key: HandoverKey, checked: unknown): void {
        this.handover.update((prev) => ({ ...prev, [key]: checked === true }))
      }
    
      handleSubmit(): void {
        this.isSubmitted.set(true)
      }
    
      handleReset(): void {
        this.isSubmitted.set(false)
      }
    }
    
  • src/app/components/blocks/exit-interview-survey/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/exit-interview-survey.json