OSAGI

SLA Timer

Component

About

SLA timer for help desks: a dial that drains and warms from primary to destructive as the due time nears, rolling digits, an overdue arc once breached, and a pill for lists. One announcement per change. From the Support & Requests Kit → ai.osagi.finance/kits/support-requests

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds SLA Timer to your project and fits it to your theme.

Code

How SLA Timer is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import { useEffect, useState } from "react";
import { SlaTimer } from "@/components/ui/sla-timer";

const settings = {
  label: "First response · ticket 1043",
  showList: true,
  /** Demo seconds that pass in one real second; 0 stands the clock still. */
  speed: 60,
  /** Share of the time after which a clock warns. */
  warnAt: 0.75,
};

const SECOND = 1000;
const MINUTE = 60 * SECOND;
/** One tick a second: the digits roll once and rest, instead of rolling without a pause. */
const TICK_MS = 1000;

/** The demo's own clock, from a fixed moment so every visit shows the same story. */
const START = Date.parse("2026-10-18T10:00:00.000Z");
const at = (minutes: number) => START + minutes * MINUTE;
/** The first response runs four minutes over, then the story starts again. */
const LOOP_AT = at(8);

const TICKETS = [
  {
    dueAt: at(225),
    id: "1044",
    startedAt: at(-15),
    subject: "Add a cost centre to requests",
  },
  {
    dueAt: at(90),
    id: "1045",
    pausedSince: at(-30),
    startedAt: at(-60),
    subject: "Waiting for the customer's logs",
  },
  {
    dueAt: at(60),
    id: "1046",
    startedAt: at(-40),
    stoppedAt: at(-5),
    subject: "Password reset link expired",
  },
];

/** A help desk's clocks: a first response that runs out, a resolution on track, and a list. */
export default function Demo(props: Partial<typeof settings>) {
  const { label, showList, speed, warnAt } = { ...settings, ...props };
  const [now, setNow] = useState(START);
  useEffect(() => {
    if (speed <= 0) {
      return;
    }
    const id = setInterval(
      () =>
        setNow((current) => {
          const next = current + speed * TICK_MS;
          return next > LOOP_AT ? START : next;
        }),
      TICK_MS
    );
    return () => clearInterval(id);
  }, [speed]);

  return (
    <div className="flex min-h-[560px] w-full flex-col items-center justify-center gap-5 bg-background p-6">
      <div className="grid w-full max-w-3xl justify-items-center gap-4 md:grid-cols-2">
        <SlaTimer
          dueAt={at(4)}
          label={label}
          now={now}
          startedAt={at(-16)}
          warnAt={warnAt}
        />
        <SlaTimer
          dueAt={at(110)}
          label="Resolution · ticket 1042"
          now={now}
          startedAt={at(-130)}
          warnAt={warnAt}
        />
      </div>
      {showList ? (
        <ul
          aria-label="Open tickets"
          className="w-full max-w-3xl divide-y divide-border rounded-2xl border border-border bg-card"
        >
          {TICKETS.map((ticket) => (
            <li
              className="flex flex-wrap items-center justify-between gap-3 px-4 py-3 text-sm"
              key={ticket.id}
            >
              <span className="min-w-0">
                <span className="text-muted-foreground">{ticket.id}</span>{" "}
                {ticket.subject}
              </span>
              <SlaTimer
                dueAt={ticket.dueAt}
                label={`Ticket ${ticket.id}`}
                now={now}
                pausedSince={ticket.pausedSince ?? null}
                startedAt={ticket.startedAt}
                stoppedAt={ticket.stoppedAt ?? null}
                variant="pill"
                warnAt={warnAt}
              />
            </li>
          ))}
        </ul>
      ) : null}
    </div>
  );
}

Dependencies

More by OSAGI

View all
  • AI Orb

    AI Orb

    83
  • KPI Tile

    KPI Tile

    28
  • OTP Input

    OTP Input

    12
  • Reasoning Thread

    Reasoning Thread

    8
  • Assistant Composer

    Assistant Composer

    8
  • Release Notes

    Release Notes

  • Docs TOC

    Docs TOC

  • Docs Search

    Docs Search