# Jobtracker dashboard

A shadcn/ui block from the [@watermelon](https://ui.watermelon.sh) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/watermelon/jobtracker-dashboard).

- Page, with a live preview: https://shadcn.exchange/r/watermelon/jobtracker-dashboard
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/watermelon/jobtracker-dashboard-light-6edddeccab1a9f26.png
- Registry item: https://registry.watermelon.sh/r/jobtracker-dashboard.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://registry.watermelon.sh/r/jobtracker-dashboard.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@dnd-kit/core`, `@dnd-kit/modifiers`, `@dnd-kit/sortable`, `@dnd-kit/utilities`, `lucide-react`, `next-themes`
- Registry items: `avatar`, `badge`, `button`, `card`, `dropdown-menu`, `input`, `select`, `separator`, `sidebar`, `table`, `tabs`, `tooltip`, `use-mobile`, `utils`

## Files

### `src/components/dashboards/jobtracker-dashboard/assets/icons.tsx`

```tsx
import type { SVGProps } from "react";

type IconProps = SVGProps<SVGSVGElement>;

export function CollapseIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M17.3994 4.36407C19.0563 4.36407 20.3994 5.70722 20.3994 7.36407V16.6365C20.3994 18.2934 19.0563 19.6365 17.3994 19.6365H6.59961C4.94276 19.6365 3.59961 18.2934 3.59961 16.6365V7.36407C3.59961 5.70722 4.94276 4.36407 6.59961 4.36407H17.3994ZM12.625 5.89142C11.5206 5.89158 10.6251 6.78704 10.625 7.89142V16.1102C10.6252 17.2145 11.5207 18.11 12.625 18.1102H16.7197C17.8242 18.1102 18.7195 17.2146 18.7197 16.1102V7.89142C18.7196 6.78694 17.8242 5.89142 16.7197 5.89142H12.625Z"
        fill="currentColor"
      />
    </svg>
  );
}

export function CustomersIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 20 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M10.8337 9.16665C10.8337 7.3257 9.34124 5.83331 7.50033 5.83331C5.65938 5.83331 4.16699 7.3257 4.16699 9.16665C4.16699 11.0076 5.65938 12.5 7.50033 12.5C9.34124 12.5 10.8337 11.0076 10.8337 9.16665Z"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M9.19916 6.29811C9.17791 6.14623 9.16699 5.99106 9.16699 5.83333C9.16699 3.99238 10.6594 2.5 12.5003 2.5C14.3412 2.5 15.8337 3.99238 15.8337 5.83333C15.8337 7.67428 14.3412 9.16667 12.5003 9.16667C11.8798 9.16667 11.299 8.99717 10.8015 8.70192"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M12.5 17.5C12.5 14.7386 10.2614 12.5 7.5 12.5C4.73857 12.5 2.5 14.7386 2.5 17.5"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M17.5 14.1667C17.5 11.4053 15.2614 9.16669 12.5 9.16669"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function JobsIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 20 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M7.08301 5.41667C7.08301 4.24628 7.08301 3.66109 7.36389 3.24072C7.48549 3.05873 7.64174 2.90248 7.82372 2.78088C8.2441 2.5 8.82926 2.5 9.99967 2.5C11.1701 2.5 11.7553 2.5 12.1756 2.78088C12.3576 2.90248 12.5138 3.05873 12.6354 3.24072C12.9163 3.66109 12.9163 4.24628 12.9163 5.41667"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M18.3337 11.6667V11.25C18.3337 8.50019 18.3337 7.12523 17.4794 6.27096C16.6251 5.41669 15.2502 5.41669 12.5003 5.41669H7.50033C4.75047 5.41669 3.37553 5.41669 2.52127 6.27096C1.66699 7.12523 1.66699 8.50019 1.66699 11.25V11.6667C1.66699 14.4165 1.66699 15.7914 2.52127 16.6458C3.37553 17.5 4.75047 17.5 7.50033 17.5H12.5003C15.2502 17.5 16.6251 17.5 17.4794 16.6458C18.3337 15.7914 18.3337 14.4165 18.3337 11.6667Z"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M1.66699 8.33331C1.66699 8.33331 3.85998 11.6666 10.0003 11.6666C16.1407 11.6666 18.3337 8.33331 18.3337 8.33331"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinejoin="round"
      />
      <path
        d="M10 11.6667V13.3334"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function SettingsIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 20 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M12.9163 10C12.9163 11.6108 11.6105 12.9167 9.99967 12.9167C8.38884 12.9167 7.08301 11.6108 7.08301 10C7.08301 8.38918 8.38884 7.08334 9.99967 7.08334C11.6105 7.08334 12.9163 8.38918 12.9163 10Z"
        stroke="currentColor"
        strokeWidth="1.25"
      />
      <path
        d="M17.5095 11.7471C17.9444 11.6299 18.1619 11.5712 18.2477 11.459C18.3337 11.347 18.3337 11.1665 18.3337 10.8058V9.19438C18.3337 8.83363 18.3337 8.65321 18.2477 8.54113C18.1618 8.42896 17.9444 8.3703 17.5095 8.25303C15.8842 7.8147 14.8669 6.11547 15.2864 4.50077C15.4017 4.0567 15.4594 3.83468 15.4043 3.70445C15.3492 3.57423 15.1912 3.4845 14.8751 3.30501L13.4378 2.48899C13.1277 2.31287 12.9726 2.22481 12.8334 2.24356C12.6942 2.26231 12.5372 2.41899 12.223 2.73232C11.007 3.94545 8.99499 3.9454 7.77894 2.73225C7.46485 2.4189 7.30781 2.26224 7.1686 2.24348C7.0294 2.22473 6.8743 2.31279 6.5641 2.4889L5.12686 3.30494C4.81077 3.4844 4.65272 3.57414 4.59764 3.70434C4.54256 3.83455 4.60022 4.0566 4.71553 4.50071C5.1348 6.11546 4.11676 7.81474 2.49118 8.25305C2.05626 8.3703 1.8388 8.42896 1.75289 8.54105C1.66699 8.65321 1.66699 8.83363 1.66699 9.19438V10.8058C1.66699 11.1665 1.66699 11.347 1.75289 11.459C1.83878 11.5712 2.05625 11.6299 2.49118 11.7471C4.11649 12.1855 5.13373 13.8847 4.71426 15.4994C4.5989 15.9435 4.54122 16.1655 4.59629 16.2957C4.65138 16.426 4.80943 16.5157 5.12553 16.6951L6.56278 17.5112C6.873 17.6873 7.02811 17.7754 7.16733 17.7566C7.30654 17.7379 7.46356 17.5811 7.77758 17.2678C8.99424 16.0537 11.0077 16.0536 12.2244 17.2677C12.5384 17.5811 12.6954 17.7378 12.8347 17.7565C12.9738 17.7753 13.129 17.6872 13.4392 17.5111L14.8764 16.695C15.1926 16.5156 15.3507 16.4259 15.4057 16.2956C15.4607 16.1654 15.4031 15.9434 15.2877 15.4993C14.868 13.8847 15.8844 12.1855 17.5095 11.7471Z"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
      />
    </svg>
  );
}

export function LogoutIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 20 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M3.66023 3.33405C3.33301 3.84663 3.33301 4.5097 3.33301 5.83585V14.1642C3.33301 15.4902 3.33301 16.1533 3.66023 16.6659C3.71867 16.7575 3.78417 16.8443 3.85615 16.9257C4.25921 17.381 4.89689 17.5632 6.17226 17.9275C7.45084 18.2927 8.09014 18.4753 8.55301 18.2014C8.63334 18.1539 8.70801 18.0976 8.77576 18.0335C9.16634 17.6637 9.16634 16.999 9.16634 15.6695V4.3305C9.16634 3.001 9.16634 2.33625 8.77576 1.9665C8.70801 1.90239 8.63334 1.84605 8.55301 1.79855C8.09014 1.52462 7.45084 1.70724 6.17226 2.07248C4.89689 2.4368 4.25921 2.61897 3.85615 3.07436C3.78417 3.15569 3.71867 3.24251 3.66023 3.33405Z"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M9.16699 3.33334H10.8479C12.4327 3.33334 13.225 3.33334 13.7173 3.8215C13.9918 4.09366 14.1132 4.45829 14.167 5.00001M9.16699 16.6667H10.8479C12.4327 16.6667 13.225 16.6667 13.7173 16.1785C13.9918 15.9063 14.1132 15.5418 14.167 15"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M17.5003 10H11.667M16.2503 7.91666C16.2503 7.91666 18.3337 9.45104 18.3337 10C18.3337 10.549 16.2503 12.0833 16.2503 12.0833"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function BackIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M15 6C15 6 9.00001 10.4189 9 12C8.99999 13.5812 15 18 15 18"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function ForwardIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <g opacity="0.5">
        <path
          d="M9.00005 6C9.00005 6 15 10.4189 15 12C15 13.5812 9 18 9 18"
          stroke="currentColor"
          strokeWidth="1.5"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
      </g>
    </svg>
  );
}

export function NotificationIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 20 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M12.9163 15C12.9163 16.6108 11.6105 17.9167 9.99967 17.9167C8.38884 17.9167 7.08301 16.6108 7.08301 15"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M16.0259 15H3.97406C3.15996 15 2.5 14.34 2.5 13.5259C2.5 13.135 2.6553 12.7601 2.93174 12.4836L3.43443 11.9809C3.90327 11.5121 4.16667 10.8761 4.16667 10.2131V7.91665C4.16667 4.69499 6.77834 2.08331 10 2.08331C13.2217 2.08331 15.8333 4.69498 15.8333 7.91665V10.2131C15.8333 10.8761 16.0967 11.5121 16.5656 11.9809L17.0682 12.4836C17.3447 12.7601 17.5 13.135 17.5 13.5259C17.5 14.34 16.84 15 16.0259 15Z"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function MenuIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 16 16"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M12 6.00003C12 6.00003 9.05407 10 8 10C6.94587 10 4 6 4 6"
        stroke="currentColor"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function SearchIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 20 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M14.167 14.1667L17.5003 17.5"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M15.8333 9.16667C15.8333 5.48477 12.8486 2.5 9.16667 2.5C5.48477 2.5 2.5 5.48477 2.5 9.16667C2.5 12.8486 5.48477 15.8333 9.16667 15.8333C12.8486 15.8333 15.8333 12.8486 15.8333 9.16667Z"
        stroke="currentColor"
        strokeWidth="1.25"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function AddIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 20 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M17.5 10C17.5 10.1658 17.4342 10.3247 17.3169 10.4419C17.1997 10.5592 17.0408 10.625 16.875 10.625H10.625V16.875C10.625 17.0408 10.5592 17.1997 10.4419 17.3169C10.3247 17.4342 10.1658 17.5 10 17.5C9.83424 17.5 9.67527 17.4342 9.55806 17.3169C9.44085 17.1997 9.375 17.0408 9.375 16.875V10.625H3.125C2.95924 10.625 2.80027 10.5592 2.68306 10.4419C2.56585 10.3247 2.5 10.1658 2.5 10C2.5 9.83424 2.56585 9.67527 2.68306 9.55806C2.80027 9.44085 2.95924 9.375 3.125 9.375H9.375V3.125C9.375 2.95924 9.44085 2.80027 9.55806 2.68306C9.67527 2.56585 9.83424 2.5 10 2.5C10.1658 2.5 10.3247 2.56585 10.4419 2.68306C10.5592 2.80027 10.625 2.95924 10.625 3.125V9.375H16.875C17.0408 9.375 17.1997 9.44085 17.3169 9.55806C17.4342 9.67527 17.5 9.83424 17.5 10Z"
        fill="currentColor"
      />
    </svg>
  );
}

export function EditIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M14.0737 3.88545C14.8189 3.07808 15.1915 2.6744 15.5874 2.43893C16.5427 1.87076 17.7191 1.85309 18.6904 2.39232C19.0929 2.6158 19.4769 3.00812 20.245 3.79276C21.0131 4.5774 21.3972 4.96972 21.6159 5.38093C22.1438 6.37312 22.1265 7.57479 21.5703 8.5507C21.3398 8.95516 20.9446 9.33578 20.1543 10.097L10.7506 19.1543C9.25288 20.5969 8.504 21.3182 7.56806 21.6837C6.63212 22.0493 5.6032 22.0224 3.54536 21.9686L3.26538 21.9613C2.63891 21.9449 2.32567 21.9367 2.14359 21.73C1.9615 21.5234 1.98636 21.2043 2.03608 20.5662L2.06308 20.2197C2.20301 18.4235 2.27297 17.5255 2.62371 16.7182C2.97444 15.9109 3.57944 15.2555 4.78943 13.9445L14.0737 3.88545Z"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinejoin="round"
      />
      <path
        d="M13 4L20 11"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinejoin="round"
      />
      <path
        d="M14 22H22"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function DeleteIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M19.5 5.5L18.8803 15.5251C18.7219 18.0864 18.6428 19.3671 18.0008 20.2879C17.6833 20.7431 17.2747 21.1273 16.8007 21.416C15.8421 22 14.559 22 11.9927 22C9.42312 22 8.1383 22 7.17905 21.4149C6.7048 21.1257 6.296 20.7408 5.97868 20.2848C5.33688 19.3626 5.25945 18.0801 5.10461 15.5152L4.5 5.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
      <path
        d="M3 5.5H21M16.0557 5.5L15.3731 4.09173C14.9196 3.15626 14.6928 2.68852 14.3017 2.39681C14.215 2.3321 14.1231 2.27454 14.027 2.2247C13.5939 2 13.0741 2 12.0345 2C10.9688 2 10.436 2 9.99568 2.23412C9.8981 2.28601 9.80498 2.3459 9.71729 2.41317C9.32164 2.7167 9.10063 3.20155 8.65861 4.17126L8.05292 5.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
      <path
        d="M9.5 16.5V10.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
      <path
        d="M14.5 16.5V10.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
    </svg>
  );
}

export function JobDraftIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 12 12"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M7.60705 2.99119L8.3079 2.29031C8.695 1.90323 9.3226 1.90323 9.7097 2.29031C10.0967 2.6774 10.0967 3.30499 9.7097 3.69207L9.0088 4.39295M7.60705 2.99119L3.49011 7.10815C2.96746 7.6308 2.70613 7.8921 2.52818 8.21055C2.35023 8.529 2.1712 9.28095 2 10C2.71904 9.8288 3.47099 9.64975 3.78944 9.4718C4.10789 9.29385 4.36922 9.03255 4.89187 8.5099L9.0088 4.39295M7.60705 2.99119L9.0088 4.39295"
        stroke="currentColor"
        strokeWidth="0.75"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M5.5 10H8.5"
        stroke="currentColor"
        strokeWidth="0.75"
        strokeLinecap="round"
      />
    </svg>
  );
}

export function JobActiveIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 12 12"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M4.31407 6.3368H4.08459C3.34272 6.3368 2.97179 6.3368 2.81368 6.0922C2.65557 5.84765 2.80622 5.5069 3.10752 4.82542L4.01334 2.77661C4.28728 2.157 4.42426 1.84719 4.68998 1.67359C4.95571 1.5 5.29295 1.5 5.9675 1.5H7.0122C7.8316 1.5 8.2413 1.5 8.3958 1.76768C8.55035 2.03535 8.3471 2.39294 7.94055 3.10811L7.4046 4.05094C7.2025 4.40648 7.10145 4.58425 7.10285 4.72976C7.1047 4.91888 7.20525 5.0931 7.3677 5.1885C7.4927 5.26195 7.69635 5.26195 8.1037 5.26195C8.61865 5.26195 8.87615 5.26195 9.01025 5.3511C9.18445 5.4669 9.27565 5.6741 9.2437 5.8816C9.2191 6.0413 9.0459 6.2328 8.6995 6.61585L5.93195 9.67615C5.38835 10.2773 5.11655 10.5778 4.93403 10.4827C4.75151 10.3876 4.83916 9.9911 5.01445 9.1981L5.35785 7.6448C5.4913 7.041 5.55805 6.7391 5.39755 6.53795C5.23705 6.3368 4.92938 6.3368 4.31407 6.3368Z"
        stroke="currentColor"
        strokeWidth="0.75"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function JobCustomerIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 18 18"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M12.75 6.375C12.75 4.30393 11.071 2.625 9 2.625C6.92893 2.625 5.25 4.30393 5.25 6.375C5.25 8.44605 6.92893 10.125 9 10.125C11.071 10.125 12.75 8.44605 12.75 6.375Z"
        fill="currentColor"
      />
      <path
        d="M14.25 15.375C14.25 12.4755 11.8995 10.125 9 10.125C6.10051 10.125 3.75 12.4755 3.75 15.375H14.25Z"
        fill="currentColor"
        stroke="currentColor"
        strokeWidth="1.125"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export function JobLocationIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 18 18"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M10.2133 16.0252C9.88807 16.3297 9.4533 16.5 9.00082 16.5C8.54835 16.5 8.11365 16.3297 7.78838 16.0252C4.80977 13.2195 0.81807 10.0852 2.7647 5.53474C3.81722 3.07437 6.34375 1.5 9.00082 1.5C11.6579 1.5 14.1845 3.07437 15.237 5.53474C17.1812 10.0795 13.1992 13.2292 10.2133 16.0252Z"
        fill="currentColor"
      />
      <path
        d="M11.625 8.25C11.625 9.69975 10.4497 10.875 9 10.875C7.55025 10.875 6.375 9.69975 6.375 8.25C6.375 6.80025 7.55025 5.625 9 5.625C10.4497 5.625 11.625 6.80025 11.625 8.25Z"
        stroke="var(--card)"
        strokeWidth="1.25"
      />
    </svg>
  );
}

export function JobTimeIcon(props: IconProps) {
  return (
    <svg
      viewBox="0 0 18 18"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
      focusable="false"
      {...props}
    >
      <path
        d="M9 16.5C13.1421 16.5 16.5 13.1421 16.5 9C16.5 4.85786 13.1421 1.5 9 1.5C4.85786 1.5 1.5 4.85786 1.5 9C1.5 13.1421 4.85786 16.5 9 16.5Z"
        fill="currentColor"
      />
      <path
        d="M9 6V9L10.5 10.5"
        stroke="var(--card)"
        strokeWidth="1.125"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/assets/logo.tsx`

```tsx
import type { SVGProps } from "react";

export function Logo(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      viewBox="0 0 40 40"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      {...props}
    >
      <path
        d="M24 0H16V12.0632C15.9663 14.2434 14.1885 16 12.0005 16H0V24H8.68629C10.808 24 12.8429 23.1571 14.3431 21.6569L21.6569 14.3431C23.1571 12.8429 24 10.808 24 8.68629V0Z"
        fill="currentColor"
      />
      <path
        d="M16 40H24V27.9368C24.0337 25.7566 25.8115 24 27.9995 24H40V16H31.3137C29.192 16 27.1571 16.8429 25.6569 18.3431L18.3431 25.6569C16.8429 27.1571 16 29.192 16 31.3137V40Z"
        fill="currentColor"
      />
    </svg>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/components/dashboard/customers-content.tsx`

```tsx
import { useState } from "react";
import { EllipsisVertical, Pencil, Trash2 } from "lucide-react";

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { buttonVariants, Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";

import { AddIcon, SearchIcon } from "../../assets/icons";
import { customers } from "../../data";

export function CustomersContent() {
  return (
    <section className="@container min-w-0 px-4 pt-4 pb-4 sm:px-6 sm:pt-7 sm:pb-6 lg:px-10 lg:pb-10">
      <div className="flex flex-col gap-4 sm:gap-6 @4xl:flex-row @4xl:items-center @4xl:justify-between">
        <div className="flex items-center justify-between gap-3 @4xl:shrink-0">
          <h1 className="text-xl font-semibold tracking-tight sm:text-3xl">
            Customers
          </h1>
          <Button className="h-10 rounded-lg px-3 sm:hidden">
            Add
            <AddIcon className="size-4" />
          </Button>
        </div>
        <div className="flex w-full flex-col gap-2 sm:flex-row @4xl:w-auto">
          <div className="relative min-w-0 flex-1 sm:mr-auto sm:max-w-80 lg:max-w-96 @4xl:mr-0 @4xl:w-96 @4xl:flex-none">
            <SearchIcon className="absolute top-1/2 left-3 size-5 -translate-y-1/2 text-muted-foreground" />
            <Input
              className="h-11 rounded-xl border-0 bg-muted pl-10 shadow-none md:text-base"
              placeholder="Search by name, email, or phone…"
              aria-label="Search customers"
            />
          </div>
          <Button
            size="lg"
            className="hidden h-11 rounded-xl px-4 text-base sm:inline-flex"
          >
            Add Customer
            <AddIcon className="size-5" />
          </Button>
        </div>
      </div>

      <div className="mt-6 sm:mt-8">
        <CustomerTable />
      </div>
    </section>
  );
}

const headerClassName =
  "h-12 px-4 text-sm font-normal text-muted-foreground first:rounded-l-xl last:rounded-r-xl";
const cellClassName = "px-4 py-0";

function CustomerActions({
  name,
  onDelete,
}: {
  name: string;
  onDelete: () => void;
}) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        className={cn(
          buttonVariants({ variant: "ghost", size: "icon-lg" }),
          "rounded-full text-muted-foreground",
        )}
        aria-label={`Actions for ${name}`}
      >
        <EllipsisVertical className="size-5" />
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" className="w-36">
        <DropdownMenuItem>
          <Pencil />
          Edit
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive" onClick={onDelete}>
          <Trash2 />
          Delete
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

function CustomerTable() {
  const [customerRows, setCustomerRows] = useState(customers);

  return (
    <Table className="min-w-[46rem] table-fixed text-sm sm:text-base">
      <colgroup>
        <col className="w-[27%]" />
        <col className="w-[35%]" />
        <col className="w-[28%]" />
        <col className="w-[10%]" />
      </colgroup>
      <TableHeader className="[&_tr]:border-0">
        <TableRow className="bg-muted hover:bg-muted!">
          <TableHead className={headerClassName}>Name</TableHead>
          <TableHead className={headerClassName}>Email</TableHead>
          <TableHead className={headerClassName}>Phone</TableHead>
          <TableHead className={`${headerClassName} text-center`}>
            Action
          </TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {customerRows.map((customer) => (
          <TableRow
            key={customer.email}
            className="h-16 hover:bg-transparent! has-aria-expanded:bg-transparent!"
          >
            <TableCell className={cellClassName}>
              <div className="flex min-w-0 items-center gap-3">
                <Avatar className="size-8">
                  <AvatarImage src={customer.avatar} alt="" />
                  <AvatarFallback>{customer.name.slice(0, 2)}</AvatarFallback>
                </Avatar>
                <span className="truncate font-medium">{customer.name}</span>
              </div>
            </TableCell>
            <TableCell className={cellClassName}>
              <a
                className="block truncate text-muted-foreground hover:text-foreground"
                href={`mailto:${customer.email}`}
              >
                {customer.email}
              </a>
            </TableCell>
            <TableCell
              className={`${cellClassName} text-muted-foreground tabular-nums`}
            >
              <a
                className="hover:text-foreground"
                href={`tel:${customer.phone.replace(/\s/g, "")}`}
              >
                {customer.phone}
              </a>
            </TableCell>
            <TableCell className={`${cellClassName} text-center`}>
              <CustomerActions
                name={customer.name}
                onDelete={() =>
                  setCustomerRows((current) =>
                    current.filter(({ email }) => email !== customer.email),
                  )
                }
              />
            </TableCell>
          </TableRow>
        ))}
        {customerRows.length === 0 && (
          <TableRow className="hover:bg-transparent!">
            <TableCell
              colSpan={4}
              className="h-32 px-4 text-center text-muted-foreground"
            >
              No customers found.
            </TableCell>
          </TableRow>
        )}
      </TableBody>
    </Table>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/components/dashboard/job-details/estimates-content.tsx`

```tsx
import { useState, type CSSProperties } from "react";
import {
  closestCenter,
  DndContext,
  DragOverlay,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
  type CollisionDetection,
  type DragEndEvent,
  type DragStartEvent,
} from "@dnd-kit/core";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
import {
  arrayMove,
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import {
  ChevronDown,
  ChevronsUpDown,
  Copy,
  EllipsisVertical,
  GripVertical,
  Pencil,
  Trash2,
} from "lucide-react";

import { buttonVariants, Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";

import { AddIcon } from "../../../assets/icons";
import type { EstimateLineItem, EstimateSection } from "../../../data";

type EstimatesContentProps = {
  sections: EstimateSection[];
};

type SortDirection = "none" | "ascending" | "descending";

type SortableRowData =
  | {
      type: "section";
      sectionId: string;
      number: string;
      label: string;
    }
  | {
      type: "item";
      sectionId: string;
      itemId: string;
      number: string;
      label: string;
    };

const columnCount = 10;
const estimateUnits = ["LS", "EA", "HR", "SF"] as const;
const tableHeaderClassName =
  "h-12 px-4 text-sm font-normal text-muted-foreground first:rounded-l-xl last:rounded-r-xl";
const tableCellClassName = "px-4 py-0";
const currencyFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
});

const matchingRowCollision: CollisionDetection = (args) => {
  const activeType = args.active.data.current?.type;
  const matchingContainers = args.droppableContainers.filter(
    (container) => container.data.current?.type === activeType,
  );

  return closestCenter({
    ...args,
    droppableContainers: matchingContainers,
  });
};

export function EstimatesContent({ sections }: EstimatesContentProps) {
  const [estimateSections, setEstimateSections] = useState(sections);
  const [collapsedSectionIds, setCollapsedSectionIds] = useState<Set<string>>(
    () => new Set(),
  );
  const [sortDirection, setSortDirection] = useState<SortDirection>("none");
  const [activeRow, setActiveRow] = useState<SortableRowData | null>(null);
  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: { distance: 6 },
    }),
    useSensor(KeyboardSensor, {
      coordinateGetter: sortableKeyboardCoordinates,
    }),
  );

  function toggleSection(sectionId: string) {
    setCollapsedSectionIds((current) => {
      const next = new Set(current);
      if (next.has(sectionId)) {
        next.delete(sectionId);
      } else {
        next.add(sectionId);
      }
      return next;
    });
  }

  function sortByName() {
    const nextDirection =
      sortDirection === "ascending" ? "descending" : "ascending";
    const multiplier = nextDirection === "ascending" ? 1 : -1;

    setEstimateSections((current) =>
      current.map((section) => ({
        ...section,
        items: [...section.items].sort(
          (a, b) => a.name.localeCompare(b.name) * multiplier,
        ),
      })),
    );
    setSortDirection(nextDirection);
  }

  function updateItemUnit(sectionId: string, itemId: string, unit: string) {
    setEstimateSections((current) =>
      current.map((section) =>
        section.id === sectionId
          ? {
              ...section,
              items: section.items.map((item) =>
                item.id === itemId ? { ...item, unit } : item,
              ),
            }
          : section,
      ),
    );
  }

  function addLineItem(sectionId: string) {
    const newItem: EstimateLineItem = {
      id: crypto.randomUUID(),
      name: "New line item",
      quantity: 1,
      unit: "LS",
      unitCost: 0,
      marginPercent: 0,
    };

    setEstimateSections((current) =>
      current.map((section) =>
        section.id === sectionId
          ? { ...section, items: [...section.items, newItem] }
          : section,
      ),
    );
    setCollapsedSectionIds((current) => {
      const next = new Set(current);
      next.delete(sectionId);
      return next;
    });
    setSortDirection("none");
  }

  function duplicateLineItem(sectionId: string, itemId: string) {
    setEstimateSections((current) =>
      current.map((section) => {
        if (section.id !== sectionId) {
          return section;
        }

        const itemIndex = section.items.findIndex(({ id }) => id === itemId);
        const sourceItem = section.items[itemIndex];
        if (!sourceItem) {
          return section;
        }

        const duplicate: EstimateLineItem = {
          ...sourceItem,
          id: crypto.randomUUID(),
          name: `${sourceItem.name} copy`,
        };
        const nextItems = [...section.items];
        nextItems.splice(itemIndex + 1, 0, duplicate);

        return { ...section, items: nextItems };
      }),
    );
    setSortDirection("none");
  }

  function deleteLineItem(sectionId: string, itemId: string) {
    setEstimateSections((current) =>
      current.map((section) =>
        section.id === sectionId
          ? {
              ...section,
              items: section.items.filter(({ id }) => id !== itemId),
            }
          : section,
      ),
    );
    setSortDirection("none");
  }

  function handleDragStart(event: DragStartEvent) {
    setActiveRow(event.active.data.current as SortableRowData);
  }

  function handleDragEnd(event: DragEndEvent) {
    const activeData = event.active.data.current as SortableRowData | undefined;
    const overData = event.over?.data.current as SortableRowData | undefined;

    setActiveRow(null);

    if (!activeData || !overData || event.active.id === event.over?.id) {
      return;
    }

    if (activeData.type === "section" && overData.type === "section") {
      setEstimateSections((current) => {
        const oldIndex = current.findIndex(
          ({ id }) => id === activeData.sectionId,
        );
        const newIndex = current.findIndex(
          ({ id }) => id === overData.sectionId,
        );
        return arrayMove(current, oldIndex, newIndex);
      });
      setSortDirection("none");
      return;
    }

    if (
      activeData.type === "item" &&
      overData.type === "item" &&
      activeData.sectionId === overData.sectionId
    ) {
      setEstimateSections((current) =>
        current.map((section) => {
          if (section.id !== activeData.sectionId) {
            return section;
          }

          const oldIndex = section.items.findIndex(
            ({ id }) => id === activeData.itemId,
          );
          const newIndex = section.items.findIndex(
            ({ id }) => id === overData.itemId,
          );

          return {
            ...section,
            items: arrayMove(section.items, oldIndex, newIndex),
          };
        }),
      );
      setSortDirection("none");
    }
  }

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={matchingRowCollision}
      modifiers={[restrictToVerticalAxis]}
      onDragStart={handleDragStart}
      onDragCancel={() => setActiveRow(null)}
      onDragEnd={handleDragEnd}
    >
      <Table className="min-w-[56rem] table-fixed text-sm sm:text-base">
        <colgroup>
          <col className="w-[8%]" />
          <col className="w-[15%]" />
          <col className="w-[11%]" />
          <col className="w-[15%]" />
          <col className="w-[7%]" />
          <col className="w-[8%]" />
          <col className="w-[11%]" />
          <col className="w-[8%]" />
          <col className="w-[9%]" />
          <col className="w-[8%]" />
        </colgroup>
        <TableHeader className="[&_tr]:border-0">
          <TableRow className="bg-muted hover:bg-muted!">
            <TableHead className={tableHeaderClassName}>#</TableHead>
            <TableHead
              aria-sort={sortDirection}
              className={tableHeaderClassName}
            >
              <Button
                variant="ghost"
                size="sm"
                className="-ml-2 h-8 px-2 font-normal text-muted-foreground hover:bg-transparent"
                onClick={sortByName}
                aria-label={`Sort by name ${
                  sortDirection === "ascending" ? "descending" : "ascending"
                }`}
              >
                Name
                <ChevronsUpDown className="size-4" />
              </Button>
            </TableHead>
            <TableHead className={tableHeaderClassName}>CSI Code</TableHead>
            <TableHead className={tableHeaderClassName}>Description</TableHead>
            <TableHead className={cn(tableHeaderClassName, "text-right")}>
              Qty
            </TableHead>
            <TableHead className={tableHeaderClassName}>Unit</TableHead>
            <TableHead className={cn(tableHeaderClassName, "text-right")}>
              Unit Cost
            </TableHead>
            <TableHead className={cn(tableHeaderClassName, "text-right")}>
              Margin
            </TableHead>
            <TableHead className={cn(tableHeaderClassName, "text-right")}>
              Profit
            </TableHead>
            <TableHead className={cn(tableHeaderClassName, "text-center")}>
              Action
            </TableHead>
          </TableRow>
        </TableHeader>

        <SortableContext
          items={estimateSections.map(({ id }) => sectionSortableId(id))}
          strategy={verticalListSortingStrategy}
        >
          {estimateSections.map((section, sectionIndex) => (
            <SortableEstimateSection
              key={section.id}
              section={section}
              sectionIndex={sectionIndex}
              collapsed={collapsedSectionIds.has(section.id)}
              onToggle={() => toggleSection(section.id)}
              onAddItem={() => addLineItem(section.id)}
              onUnitChange={(itemId, unit) =>
                updateItemUnit(section.id, itemId, unit)
              }
              onDuplicateItem={(itemId) =>
                duplicateLineItem(section.id, itemId)
              }
              onDeleteItem={(itemId) => deleteLineItem(section.id, itemId)}
            />
          ))}
        </SortableContext>
      </Table>

      <DragOverlay dropAnimation={{ duration: 200, easing: "ease-out" }}>
        {activeRow ? <EstimateDragPreview row={activeRow} /> : null}
      </DragOverlay>
    </DndContext>
  );
}

type SortableEstimateSectionProps = {
  section: EstimateSection;
  sectionIndex: number;
  collapsed: boolean;
  onToggle: () => void;
  onAddItem: () => void;
  onUnitChange: (itemId: string, unit: string) => void;
  onDuplicateItem: (itemId: string) => void;
  onDeleteItem: (itemId: string) => void;
};

function SortableEstimateSection({
  section,
  sectionIndex,
  collapsed,
  onToggle,
  onAddItem,
  onUnitChange,
  onDuplicateItem,
  onDeleteItem,
}: SortableEstimateSectionProps) {
  const sectionNumber = sectionIndex + 1;
  const nextLineNumber = `${sectionNumber}.${section.items.length + 1}`;
  const {
    attributes,
    listeners,
    setNodeRef,
    setActivatorNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({
    id: sectionSortableId(section.id),
    data: {
      type: "section",
      sectionId: section.id,
      number: String(sectionNumber),
      label: section.name,
    } satisfies SortableRowData,
  });
  const style: CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
    position: "relative",
    zIndex: isDragging ? 1 : undefined,
  };

  return (
    <TableBody
      ref={setNodeRef}
      style={style}
      className={cn(isDragging && "opacity-35")}
    >
      <TableRow
        aria-hidden="true"
        className="h-3 border-0 hover:bg-transparent!"
      >
        <TableCell className="p-0" colSpan={columnCount} />
      </TableRow>
      <TableRow
        className="cursor-pointer border-0 bg-muted/70 hover:bg-muted/70!"
        role="button"
        tabIndex={0}
        aria-expanded={!collapsed}
        aria-label={`${collapsed ? "Expand" : "Collapse"} ${
          section.name
        } section`}
        onClick={onToggle}
        onKeyDown={(event) => {
          if (
            event.target === event.currentTarget &&
            (event.key === "Enter" || event.key === " ")
          ) {
            event.preventDefault();
            onToggle();
          }
        }}
      >
        <TableCell className="h-12 rounded-l-xl px-4 py-0">
          <span className="flex items-center gap-1">
            <button
              ref={setActivatorNodeRef}
              type="button"
              className="flex size-7 shrink-0 cursor-grab touch-none items-center justify-center rounded-md text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring active:cursor-grabbing"
              {...attributes}
              {...listeners}
              onClick={(event) => event.stopPropagation()}
              aria-label={`Reorder ${section.name} section`}
            >
              <GripVertical className="size-5" />
            </button>
            <span className="font-medium tabular-nums">{sectionNumber}</span>
          </span>
        </TableCell>
        <TableCell
          className="h-12 rounded-r-xl px-4 py-0 font-semibold"
          colSpan={columnCount - 1}
        >
          <span className="flex items-center justify-between gap-4">
            <span className="truncate">{section.name}</span>
            <span
              className="flex size-6 shrink-0 items-center justify-center"
              aria-hidden="true"
            >
              <ChevronDown
                className={cn(
                  "size-4 transition-transform",
                  collapsed && "-rotate-90",
                )}
              />
            </span>
          </span>
        </TableCell>
      </TableRow>

      {!collapsed && (
        <SortableContext
          items={section.items.map(({ id }) => itemSortableId(section.id, id))}
          strategy={verticalListSortingStrategy}
        >
          {section.items.map((item, itemIndex) => (
            <SortableEstimateItemRow
              key={item.id}
              item={item}
              sectionId={section.id}
              number={`${sectionNumber}.${itemIndex + 1}`}
              onUnitChange={(unit) => onUnitChange(item.id, unit)}
              onDuplicate={() => onDuplicateItem(item.id)}
              onDelete={() => onDeleteItem(item.id)}
            />
          ))}
        </SortableContext>
      )}

      {!collapsed && (
        <TableRow className="h-16 hover:bg-transparent!">
          <TableCell
            className={cn(
              tableCellClassName,
              "font-medium text-muted-foreground tabular-nums",
            )}
          >
            {nextLineNumber}
          </TableCell>
          <TableCell className={tableCellClassName} colSpan={columnCount - 1}>
            <Button
              variant="link"
              size="sm"
              className="px-0"
              onClick={onAddItem}
            >
              Add line item
              <AddIcon className="size-4" />
            </Button>
          </TableCell>
        </TableRow>
      )}
    </TableBody>
  );
}

type SortableEstimateItemRowProps = {
  item: EstimateLineItem;
  sectionId: string;
  number: string;
  onUnitChange: (unit: string) => void;
  onDuplicate: () => void;
  onDelete: () => void;
};

function SortableEstimateItemRow({
  item,
  sectionId,
  number,
  onUnitChange,
  onDuplicate,
  onDelete,
}: SortableEstimateItemRowProps) {
  const {
    attributes,
    listeners,
    setNodeRef,
    setActivatorNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({
    id: itemSortableId(sectionId, item.id),
    data: {
      type: "item",
      sectionId,
      itemId: item.id,
      number,
      label: item.name,
    } satisfies SortableRowData,
  });
  const style: CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
    position: "relative",
    zIndex: isDragging ? 2 : undefined,
  };

  return (
    <TableRow
      ref={setNodeRef}
      style={style}
      className={cn(
        "h-16 hover:bg-transparent! has-aria-expanded:bg-transparent!",
        isDragging && "opacity-35",
      )}
    >
      <TableCell className={tableCellClassName}>
        <span className="flex items-center gap-1">
          <button
            ref={setActivatorNodeRef}
            type="button"
            className="flex size-7 shrink-0 cursor-grab touch-none items-center justify-center rounded-md text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring active:cursor-grabbing"
            {...attributes}
            {...listeners}
            aria-label={`Reorder ${item.name} line item`}
          >
            <GripVertical className="size-5" />
          </button>
          <span className="font-medium tabular-nums">{number}</span>
        </span>
      </TableCell>
      <TableCell className={cn(tableCellClassName, "font-medium")}>
        {item.name}
      </TableCell>
      <TableCell className={cn(tableCellClassName, "text-muted-foreground")}>
        {item.csiCode ?? "Not set"}
      </TableCell>
      <TableCell
        className={cn(tableCellClassName, "truncate text-muted-foreground")}
        title={item.description}
      >
        {item.description ?? "No description"}
      </TableCell>
      <TableCell className={cn(tableCellClassName, "text-right tabular-nums")}>
        {item.quantity.toFixed(2)}
      </TableCell>
      <TableCell className={tableCellClassName}>
        <Select
          value={item.unit}
          onValueChange={(unit) => unit && onUnitChange(unit)}
        >
          <SelectTrigger
            size="sm"
            className="-mx-2 w-18 border-transparent bg-transparent shadow-none hover:bg-muted"
            aria-label={`Unit for ${item.name}`}
          >
            <SelectValue />
          </SelectTrigger>
          <SelectContent align="start" className="min-w-20">
            {estimateUnits.map((unit) => (
              <SelectItem key={unit} value={unit}>
                {unit}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </TableCell>
      <TableCell className={cn(tableCellClassName, "text-right tabular-nums")}>
        {currencyFormatter.format(item.unitCost)}
      </TableCell>
      <TableCell className={cn(tableCellClassName, "text-right tabular-nums")}>
        {item.marginPercent}%
      </TableCell>
      <TableCell
        className={cn(
          tableCellClassName,
          "text-right font-medium tabular-nums",
        )}
      >
        {currencyFormatter.format(calculateProfit(item))}
      </TableCell>
      <TableCell className={cn(tableCellClassName, "text-center")}>
        <LineItemActions
          itemName={item.name}
          onDuplicate={onDuplicate}
          onDelete={onDelete}
        />
      </TableCell>
    </TableRow>
  );
}

function LineItemActions({
  itemName,
  onDuplicate,
  onDelete,
}: {
  itemName: string;
  onDuplicate: () => void;
  onDelete: () => void;
}) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        className={cn(
          buttonVariants({ variant: "ghost", size: "icon-lg" }),
          "rounded-full text-muted-foreground",
        )}
        aria-label={`Actions for ${itemName}`}
      >
        <EllipsisVertical className="size-5" />
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" className="w-40">
        <DropdownMenuItem>
          <Pencil />
          Edit
        </DropdownMenuItem>
        <DropdownMenuItem onClick={onDuplicate}>
          <Copy />
          Duplicate
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive" onClick={onDelete}>
          <Trash2 />
          Delete
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

function EstimateDragPreview({ row }: { row: SortableRowData }) {
  return (
    <div className="flex min-w-72 items-center gap-3 rounded-xl bg-card px-4 py-3 text-sm shadow-lg ring-1 ring-foreground/10">
      <GripVertical className="size-5 shrink-0 text-muted-foreground" />
      <span className="font-medium tabular-nums">{row.number}</span>
      <span className="truncate font-medium">{row.label}</span>
    </div>
  );
}

function sectionSortableId(sectionId: string) {
  return `section:${sectionId}`;
}

function itemSortableId(sectionId: string, itemId: string) {
  return `item:${sectionId}:${itemId}`;
}

function calculateProfit(item: EstimateLineItem) {
  const totalCost = item.quantity * item.unitCost;
  const marginRate = item.marginPercent / 100;

  if (marginRate <= 0 || marginRate >= 1) {
    return 0;
  }

  return totalCost / (1 - marginRate) - totalCost;
}
```

### `src/components/dashboards/jobtracker-dashboard/components/dashboard/job-details/job-details-content.tsx`

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

import { EstimatesContent } from "./estimates-content";
import { jobEstimates } from "../../../data";
import { useDashboardNavigation } from "../../navigation";

const jobTabs = ["Estimates", "Plans", "Records", "Files"] as const;

export function JobDetailsContent() {
  const { pathname } = useDashboardNavigation();
  const jobId = pathname.split("/")[2];

  if (!jobId) {
    return null;
  }

  const estimate = jobEstimates.find((item) => item.jobId === jobId);

  return (
    <section className="@container min-w-0 px-4 pt-4 pb-4 sm:px-6 sm:pt-7 sm:pb-6 lg:px-10 lg:pb-10">
      <Tabs defaultValue="estimates" className="gap-0">
        <TabsList
          variant="line"
          className="h-12 w-full justify-start gap-0 border-b p-0"
        >
          {jobTabs.map((tab) => (
            <TabsTrigger
              key={tab}
              value={tab.toLowerCase()}
              className="h-full flex-none px-4 text-base font-normal after:bottom-[-1px]! data-active:text-primary data-active:after:bg-primary data-[state=active]:text-primary data-[state=active]:after:bg-primary"
            >
              {tab}
            </TabsTrigger>
          ))}
        </TabsList>

        <TabsContent value="estimates" className="mt-6">
          <EstimatesContent key={jobId} sections={estimate?.sections ?? []} />
        </TabsContent>
        {jobTabs.slice(1).map((tab) => (
          <TabsContent key={tab} value={tab.toLowerCase()} className="mt-6">
            <div className="flex h-48 items-center justify-center rounded-xl border border-dashed text-muted-foreground">
              No {tab.toLowerCase()} added yet
            </div>
          </TabsContent>
        ))}
      </Tabs>
    </section>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/components/dashboard/jobs-content.tsx`

```tsx
import type { ComponentType, SVGProps } from "react";
import { Archive, Check } from "lucide-react";

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardAction,
  CardContent,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";

import { DashboardLink } from "../navigation";

import {
  AddIcon,
  JobActiveIcon,
  JobCustomerIcon,
  JobDraftIcon,
  JobLocationIcon,
  JobTimeIcon,
  SearchIcon,
} from "../../assets/icons";
import { jobs, type Job, type JobStatus } from "../../data";

type JobColumn = {
  status: JobStatus;
  label: string;
  emptyMessage: string;
  accentClassName: string;
  countClassName: string;
};

const jobColumns: JobColumn[] = [
  {
    status: "pre-construction",
    label: "Pre- Constructions",
    emptyMessage: "No pre-construction jobs",
    accentClassName: "text-[#3283ff] dark:text-[#66a3ff]",
    countClassName:
      "bg-[#3283ff]/10 text-[#3283ff] dark:bg-[#66a3ff]/15 dark:text-[#66a3ff]",
  },
  {
    status: "active",
    label: "Active",
    emptyMessage: "No active jobs",
    accentClassName: "text-[#ff5600] dark:text-[#ff7a38]",
    countClassName:
      "bg-[#ff5600]/10 text-[#ff5600] dark:bg-[#ff7a38]/15 dark:text-[#ff7a38]",
  },
  {
    status: "complete",
    label: "Complete",
    emptyMessage: "No completed jobs",
    accentClassName: "text-[#00c238] dark:text-[#35d968]",
    countClassName:
      "bg-[#00c238]/10 text-[#00c238] dark:bg-[#35d968]/15 dark:text-[#35d968]",
  },
  {
    status: "closed",
    label: "Closed",
    emptyMessage: "No closed jobs",
    accentClassName: "text-[#c57d00] dark:text-[#e4a83e]",
    countClassName:
      "bg-[#fef6e7] text-[#c57d00] dark:bg-[#e4a83e]/[0.12] dark:text-[#e4a83e]",
  },
];

type JobBadgeDetails = {
  label: string;
  icon: ComponentType<SVGProps<SVGSVGElement>>;
  className: string;
};

const jobBadgeDetails: Record<JobStatus, JobBadgeDetails> = {
  "pre-construction": {
    label: "Draft",
    icon: JobDraftIcon,
    className:
      "bg-[#f2fff7] text-[#0e7b33] dark:bg-[#45c570]/[0.12] dark:text-[#45c570]",
  },
  active: {
    label: "Active",
    icon: JobActiveIcon,
    className:
      "bg-[#ff5600]/10 text-[#ff5600] dark:bg-[#ff7a38]/15 dark:text-[#ff7a38]",
  },
  complete: {
    label: "Complete",
    icon: Check,
    className:
      "bg-[#00c238]/10 text-[#00c238] dark:bg-[#35d968]/15 dark:text-[#35d968]",
  },
  closed: {
    label: "Closed",
    icon: Archive,
    className:
      "bg-[#fef6e7] text-[#c57d00] dark:bg-[#e4a83e]/[0.12] dark:text-[#e4a83e]",
  },
};

export function JobsContent() {
  return (
    <section className="@container min-w-0 px-4 pt-4 pb-4 sm:px-6 sm:pt-7 sm:pb-6 lg:px-10 lg:pb-10">
      <div className="flex flex-col gap-4 sm:gap-6 @4xl:flex-row @4xl:items-center @4xl:justify-between">
        <div className="flex items-center justify-between gap-3 @4xl:shrink-0">
          <h1 className="text-xl font-semibold tracking-tight sm:text-3xl">
            Jobs
          </h1>
          <Button className="h-10 rounded-lg px-3 sm:hidden">
            Add
            <AddIcon className="size-4" />
          </Button>
        </div>
        <div className="flex w-full flex-col gap-2 sm:flex-row @4xl:w-auto">
          <div className="relative min-w-0 flex-1 sm:mr-auto sm:max-w-80 lg:max-w-96 @4xl:mr-0 @4xl:w-96 @4xl:flex-none">
            <SearchIcon className="absolute top-1/2 left-3 size-5 -translate-y-1/2 text-muted-foreground" />
            <Input
              className="h-11 rounded-xl border-0 bg-muted pl-10 shadow-none md:text-base"
              placeholder="Search job list"
              aria-label="Search jobs"
            />
          </div>
          <Button
            size="lg"
            className="hidden h-11 rounded-xl px-4 text-base sm:inline-flex"
          >
            Add Job
            <AddIcon className="size-5" />
          </Button>
        </div>
      </div>

      <JobBoard />
    </section>
  );
}

function JobBoard() {
  return (
    <div className="mt-6 grid items-start gap-3 sm:mt-8 @2xl:grid-cols-2 @4xl:grid-cols-3 @5xl:grid-cols-4">
      {jobColumns.map((column) => {
        const columnJobs = jobs.filter((job) => job.status === column.status);

        return (
          <JobBoardColumn
            key={column.status}
            column={column}
            jobs={columnJobs}
          />
        );
      })}
    </div>
  );
}

function JobBoardColumn({ column, jobs }: { column: JobColumn; jobs: Job[] }) {
  return (
    <section
      className="rounded-2xl bg-sidebar p-1 pt-4"
      aria-labelledby={`${column.status}-heading`}
    >
      <div className="mb-2 flex h-7 items-center justify-between pr-2">
        <h2
          id={`${column.status}-heading`}
          className="flex items-center gap-2 font-medium"
        >
          <JobColumnMarker className={column.accentClassName} />
          <span>{column.label}</span>
        </h2>
        <Badge
          variant="secondary"
          className={cn("h-6 rounded-lg font-normal", column.countClassName)}
        >
          {jobs.length}
        </Badge>
      </div>

      <div className="space-y-2">
        {jobs.length ? (
          jobs.map((job) => <JobCard key={job.id} job={job} />)
        ) : (
          <div className="flex h-48 items-center justify-center rounded-xl border border-dashed bg-card px-3">
            <p>{column.emptyMessage}</p>
          </div>
        )}
      </div>
    </section>
  );
}

function JobColumnMarker({ className }: { className: string }) {
  return (
    <span
      className={cn("flex h-7 w-1 shrink-0 flex-col gap-0.5", className)}
      aria-hidden="true"
    >
      {[0, 1, 2].map((segment) => (
        <span
          key={segment}
          className="h-2 w-full bg-current [clip-path:polygon(0_25%,100%_0,100%_75%,0_100%)]"
        />
      ))}
    </span>
  );
}

function JobCard({ job }: { job: Job }) {
  return (
    <DashboardLink
      href={`/jobs/${job.id}/estimates`}
      className="block rounded-xl outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
      aria-label={`Open ${job.title}`}
    >
      <Card className="gap-0 py-0 shadow-none ring-0 transition-colors hover:bg-accent">
        <CardHeader className="px-4 py-3">
          <CardTitle>{job.title}</CardTitle>
          <CardAction>
            <JobStatusBadge status={job.status} />
          </CardAction>
        </CardHeader>
        <Separator />

        <CardContent className="space-y-3 px-4 py-4">
          <div className="flex h-6 items-center justify-between gap-3">
            <JobDetailLabel
              icon={JobCustomerIcon}
              iconClassName="text-[#15803d] dark:text-[#4ade80]"
            >
              Customer
            </JobDetailLabel>
            <div className="flex min-w-0 items-center gap-1 font-medium text-muted-foreground">
              <Avatar className="size-5">
                <AvatarImage src={job.customer.avatar} alt="" />
                <AvatarFallback>{job.customer.name.slice(0, 2)}</AvatarFallback>
              </Avatar>
              <span className="truncate">{job.customer.name}</span>
            </div>
          </div>

          <div className="flex items-start justify-between gap-3">
            <JobDetailLabel
              icon={JobLocationIcon}
              iconClassName="text-[#1d4ed8] dark:text-[#60a5fa]"
            >
              Location
            </JobDetailLabel>
            <span className="max-w-48 text-right font-medium text-muted-foreground">
              {job.location}
            </span>
          </div>

          <div className="flex h-6 items-center justify-between gap-3">
            <JobDetailLabel
              icon={JobTimeIcon}
              iconClassName="text-[#7c3aed] dark:text-[#a78bfa]"
            >
              Time
            </JobDetailLabel>
            <span className="font-medium text-muted-foreground">
              {job.time}
            </span>
          </div>
        </CardContent>
      </Card>
    </DashboardLink>
  );
}

function JobStatusBadge({ status }: { status: JobStatus }) {
  const details = jobBadgeDetails[status];
  const Icon = details.icon;

  return (
    <Badge
      variant="secondary"
      className={cn("h-6 rounded-full px-2.5 font-normal", details.className)}
    >
      <Icon />
      {details.label}
    </Badge>
  );
}

function JobDetailLabel({
  icon: Icon,
  iconClassName,
  children,
}: {
  icon: ComponentType<SVGProps<SVGSVGElement>>;
  iconClassName: string;
  children: string;
}) {
  return (
    <span className="flex shrink-0 items-center gap-2 text-muted-foreground">
      <Icon className={cn("size-4", iconClassName)} />
      {children}
    </span>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/components/layout/app-sidebar.tsx`

```tsx
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";

import { Button } from "@/components/ui/button";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarHeader,
  SidebarMenu,
  useSidebar,
} from "@/components/ui/sidebar";

import {
  type NavigationItem,
  SidebarNavigationItem,
} from "./sidebar-navigation-item";
import {
  CustomersIcon,
  JobsIcon,
  LogoutIcon,
  SettingsIcon,
} from "../../assets/icons";
import { Logo } from "../../assets/logo";
import { DashboardLink } from "../navigation";

type SidebarItem = NavigationItem & {
  trailing?: "theme-toggle";
};

const primaryNavigationItems = [
  { label: "Customers", href: "/", icon: CustomersIcon },
  { label: "Jobs", href: "/jobs", icon: JobsIcon },
] satisfies NavigationItem[];

const utilityNavigationItems = [
  { label: "Settings", icon: SettingsIcon, trailing: "theme-toggle" },
  { label: "Logout", icon: LogoutIcon, variant: "destructive" },
] satisfies SidebarItem[];

function Brand() {
  const { isMobile, setOpenMobile } = useSidebar();

  return (
    <div className="flex items-center px-3">
      <DashboardLink
        href="/"
        aria-label="JobTracker home"
        className="flex max-w-48 min-w-0 items-center gap-2 overflow-hidden rounded-md transition-[max-width,opacity] duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring group-data-[collapsible=icon]:max-w-0 group-data-[collapsible=icon]:opacity-0"
        onClick={() => {
          if (isMobile) {
            setOpenMobile(false);
          }
        }}
      >
        <Logo className="size-5 shrink-0 text-primary" aria-hidden="true" />
        <span className="whitespace-nowrap text-lg font-semibold tracking-tight">
          JOB <span className="text-primary">TRACKER</span>
        </span>
      </DashboardLink>
    </div>
  );
}

function ThemeToggle() {
  const { resolvedTheme, setTheme } = useTheme();
  const dark = resolvedTheme === "dark";

  return (
    <Button
      variant="ghost"
      size="icon-lg"
      className="size-10 shrink-0 rounded-xl text-muted-foreground hover:!bg-background hover:!text-foreground group-data-[collapsible=icon]:hidden"
      onClick={() => setTheme(dark ? "light" : "dark")}
      aria-label={`Switch to ${dark ? "light" : "dark"} theme`}
    >
      <Sun className="size-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute size-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
    </Button>
  );
}

export function AppSidebar() {
  return (
    <Sidebar collapsible="offcanvas" className="border-none">
      <SidebarHeader className="gap-6 p-3 transition-[padding] duration-200 group-data-[collapsible=icon]:p-2">
        <Brand />
        <SidebarMenu className="gap-2">
          {primaryNavigationItems.map((item) => (
            <SidebarNavigationItem key={item.label} item={item} />
          ))}
        </SidebarMenu>
      </SidebarHeader>

      <SidebarContent />

      <SidebarFooter className="p-3 transition-[padding] duration-200 group-data-[collapsible=icon]:p-2">
        <SidebarMenu className="gap-2">
          {utilityNavigationItems.map((item) => (
            <SidebarNavigationItem
              key={item.label}
              item={item}
              trailing={
                item.trailing === "theme-toggle" ? <ThemeToggle /> : undefined
              }
            />
          ))}
        </SidebarMenu>
      </SidebarFooter>
    </Sidebar>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/components/layout/dashboard-shell.tsx`

```tsx
import type { CSSProperties, ReactNode } from "react";

import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { TooltipProvider } from "@/components/ui/tooltip";

import { AppSidebar } from "./app-sidebar";
import { TopNavbar } from "./top-navbar";

type DashboardShellProps = {
  children: ReactNode;
};

export function DashboardShell({ children }: DashboardShellProps) {
  return (
    <TooltipProvider>
      <SidebarProvider
        open
        className="jobtracker-dashboard h-svh min-h-0 overflow-hidden"
        style={
          {
            "--sidebar-width": "17rem",
            "--sidebar-width-icon": "3.5rem",
          } as CSSProperties
        }
      >
        <AppSidebar />
        <SidebarInset className="h-svh overflow-hidden bg-background">
          <TopNavbar />
          <div className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto">
            {children}
          </div>
        </SidebarInset>
      </SidebarProvider>
    </TooltipProvider>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/components/layout/sidebar-navigation-item.tsx`

```tsx
import type { ComponentType, ReactNode, SVGProps } from "react";

import {
  SidebarMenuButton,
  SidebarMenuItem,
  useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";

import { useDashboardNavigation } from "../navigation";

export type NavigationItem = {
  label: string;
  icon: ComponentType<SVGProps<SVGSVGElement>>;
  href?: string;
  active?: boolean;
  variant?: "default" | "destructive";
};

export const sidebarNavigationButtonClassName =
  "h-10 gap-3 rounded-xl px-3 text-base font-medium tracking-tight text-muted-foreground data-active:bg-background data-active:text-primary data-active:hover:bg-background data-active:hover:text-primary [&_svg]:size-5";

export const sidebarNavigationLabelClassName =
  "max-w-40 whitespace-nowrap opacity-100 transition-[max-width,opacity] duration-200 group-data-[collapsible=icon]:max-w-0 group-data-[collapsible=icon]:opacity-0";

type SidebarNavigationItemProps = {
  item: NavigationItem;
  trailing?: ReactNode;
};

export function SidebarNavigationItem({
  item,
  trailing,
}: SidebarNavigationItemProps) {
  const Icon = item.icon;
  const { navigate, pathname } = useDashboardNavigation();
  const { isMobile, setOpenMobile } = useSidebar();
  const isActive =
    item.active ??
    (item.href === "/"
      ? pathname === item.href
      : Boolean(item.href && pathname.startsWith(item.href)));
  const content = (
    <>
      <Icon />
      <span className={sidebarNavigationLabelClassName}>{item.label}</span>
    </>
  );

  if (item.href) {
    return (
      <SidebarMenuItem className={cn(trailing && "flex items-center gap-1")}>
        <SidebarMenuButton
          isActive={isActive}
          tooltip={item.label}
          className={cn(
            sidebarNavigationButtonClassName,
            !isActive &&
              item.variant !== "destructive" &&
              "hover:!bg-background hover:!text-foreground",
            item.variant === "destructive" &&
              "text-destructive hover:bg-destructive/10 hover:text-destructive active:bg-destructive/10 active:text-destructive",
            trailing && "min-w-0 flex-1",
          )}
          onClick={() => {
            navigate(item.href!);
            if (isMobile) {
              setOpenMobile(false);
            }
          }}
        >
          {content}
        </SidebarMenuButton>
        {trailing}
      </SidebarMenuItem>
    );
  }

  return (
    <SidebarMenuItem className={cn(trailing && "flex items-center gap-1")}>
      <SidebarMenuButton
        isActive={isActive}
        tooltip={item.label}
        className={cn(
          sidebarNavigationButtonClassName,
          !isActive &&
            item.variant !== "destructive" &&
            "hover:!bg-background hover:!text-foreground",
          item.variant === "destructive" &&
            "text-destructive hover:bg-destructive/10 hover:text-destructive active:bg-destructive/10 active:text-destructive",
          trailing && "min-w-0 flex-1",
        )}
      >
        {content}
      </SidebarMenuButton>
      {trailing}
    </SidebarMenuItem>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/components/layout/top-navbar.tsx`

```tsx
import {
  CustomersIcon,
  JobsIcon,
  MenuIcon,
  NotificationIcon,
} from "../../assets/icons";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { buttonVariants, Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";

import { currentUser, notifications } from "../../data";
import { CircleHelp, LogOut, Settings, UserRound } from "lucide-react";
import { useDashboardNavigation } from "../navigation";

export function TopNavbar() {
  const isMobile = useIsMobile();
  const { pathname } = useDashboardNavigation();
  const currentPage = pathname.startsWith("/jobs")
    ? { label: "Jobs", icon: JobsIcon }
    : { label: "Customers", icon: CustomersIcon };
  const PageIcon = currentPage.icon;

  return (
    <header className="flex h-16 shrink-0 items-center justify-between px-4 sm:px-6 lg:px-10">
      <div className="flex min-w-0 items-center gap-3">
        <SidebarTrigger
          size="icon-lg"
          className="md:hidden"
          aria-label="Toggle sidebar"
        />
        <PageIcon className="hidden size-5 shrink-0 md:block" />
        <span className="truncate text-lg font-medium tracking-tight">
          {currentPage.label}
        </span>
      </div>

      <div className="flex shrink-0 items-center gap-2">
        <NotificationsMenu
          align={isMobile ? "center" : "end"}
          collisionPadding={isMobile ? 16 : 5}
        />
        <ProfileMenu
          align={isMobile ? "center" : "end"}
          collisionPadding={isMobile ? 16 : 5}
        />
      </div>
    </header>
  );
}

type MenuPositioningProps = {
  align: "center" | "end";
  collisionPadding: number;
};

function NotificationsMenu({ align, collisionPadding }: MenuPositioningProps) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        className={cn(
          buttonVariants({ variant: "secondary", size: "icon-lg" }),
          "relative rounded-full",
        )}
        aria-label="Open notifications"
      >
        <NotificationIcon className="size-5" />
        <span className="absolute top-2 right-2 size-2 rounded-full bg-red-500 ring-2 ring-sidebar" />
      </DropdownMenuTrigger>
      <DropdownMenuContent
        align={align}
        sideOffset={8}
        collisionPadding={collisionPadding}
        className="w-72 rounded-xl p-2"
      >
        <DropdownMenuGroup>
          <DropdownMenuLabel className="px-2 py-2 text-sm">
            Notifications
          </DropdownMenuLabel>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          {notifications.slice(0, 3).map((notification) => (
            <DropdownMenuItem
              key={notification.id}
              className="block rounded-lg py-2.5"
            >
              <span className="flex min-w-0 items-center justify-between gap-3">
                <span className="truncate font-medium">
                  {notification.title}
                </span>
                <time className="shrink-0 text-xs text-muted-foreground">
                  {notification.timestamp}
                </time>
              </span>
              <span className="mt-0.5 block truncate text-sm text-muted-foreground">
                {notification.description}
              </span>
            </DropdownMenuItem>
          ))}
        </DropdownMenuGroup>
        <div className="mt-1 border-t pt-1">
          <Button variant="link" size="sm" className="w-full">
            View all notifications
          </Button>
        </div>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

function ProfileMenu({ align, collisionPadding }: MenuPositioningProps) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        className={cn(
          buttonVariants({ variant: "secondary", size: "lg" }),
          "gap-1 rounded-full p-1 pr-1.5",
        )}
        aria-label="Open profile menu"
      >
        <Avatar className="size-8">
          <AvatarImage src={currentUser.avatar} alt={currentUser.name} />
          <AvatarFallback>VP</AvatarFallback>
        </Avatar>
        <MenuIcon className="size-4" />
      </DropdownMenuTrigger>
      <DropdownMenuContent
        align={align}
        sideOffset={8}
        collisionPadding={collisionPadding}
        className="w-72 rounded-xl p-2"
      >
        <DropdownMenuGroup>
          <DropdownMenuLabel className="flex items-center gap-3 px-2 py-2 text-popover-foreground">
            <Avatar className="size-9">
              <AvatarImage src={currentUser.avatar} alt="VP" />
              <AvatarFallback>VP</AvatarFallback>
            </Avatar>
            <span className="min-w-0">
              <span className="block truncate font-medium">
                {currentUser.name}
              </span>
              <span className="block truncate font-normal text-muted-foreground">
                {currentUser.email}
              </span>
            </span>
          </DropdownMenuLabel>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuItem className="rounded-lg">
            <UserRound />
            Profile
          </DropdownMenuItem>
          <DropdownMenuItem className="rounded-lg">
            <Settings />
            Settings
          </DropdownMenuItem>
          <DropdownMenuItem className="rounded-lg">
            <CircleHelp />
            Help and support
          </DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuGroup className="mt-1 border-t pt-1">
          <DropdownMenuItem variant="destructive" className="rounded-lg">
            <LogOut />
            Log out
          </DropdownMenuItem>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/components/navigation.tsx`

```tsx
import {
  createContext,
  useContext,
  useMemo,
  useState,
  type AnchorHTMLAttributes,
  type MouseEvent,
  type ReactNode,
} from "react";

type DashboardNavigation = {
  pathname: string;
  navigate: (pathname: string) => void;
};

const DashboardNavigationContext = createContext<DashboardNavigation | null>(
  null,
);

export function DashboardNavigationProvider({
  children,
}: {
  children: ReactNode;
}) {
  const [pathname, setPathname] = useState("/");
  const value = useMemo(
    () => ({ pathname, navigate: setPathname }),
    [pathname],
  );

  return (
    <DashboardNavigationContext.Provider value={value}>
      {children}
    </DashboardNavigationContext.Provider>
  );
}

export function useDashboardNavigation() {
  const context = useContext(DashboardNavigationContext);

  if (!context) {
    throw new Error(
      "useDashboardNavigation must be used within DashboardNavigationProvider",
    );
  }

  return context;
}

export function DashboardLink({
  href,
  onClick,
  ...props
}: AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }) {
  const { navigate } = useDashboardNavigation();

  function handleClick(event: MouseEvent<HTMLAnchorElement>) {
    onClick?.(event);

    if (
      event.defaultPrevented ||
      event.button !== 0 ||
      event.metaKey ||
      event.ctrlKey ||
      event.shiftKey ||
      event.altKey
    ) {
      return;
    }

    event.preventDefault();
    navigate(href);
  }

  return <a href={href} onClick={handleClick} {...props} />;
}
```

### `src/components/dashboards/jobtracker-dashboard/dashboard.css`

```css
.jobtracker-dashboard,
body:has(.jobtracker-dashboard) [data-mobile="true"][data-sidebar="sidebar"],
body:has(.jobtracker-dashboard) [data-slot="dropdown-menu-content"],
body:has(.jobtracker-dashboard) [data-slot="select-content"],
body:has(.jobtracker-dashboard) [data-slot="sheet-content"],
body:has(.jobtracker-dashboard) [data-slot="tooltip-content"] {
  --background: #ffffff;
  --foreground: #0a1b39;
  --card: #ffffff;
  --card-foreground: #0a1b39;
  --popover: #ffffff;
  --popover-foreground: #0a1b39;
  --primary: #ea580b;
  --primary-foreground: #ffffff;
  --secondary: #f7f9fc;
  --secondary-foreground: #0a1b39;
  --muted: #f7f7f7;
  --muted-foreground: #485066;
  --accent: #f7f7f7;
  --accent-foreground: #0a1b39;
  --destructive: #ff0000;
  --border: #e4e7ec;
  --input: #f7f7f7;
  --ring: #ea580b;
  --sidebar: #f8f9fb;
  --sidebar-foreground: #485066;
  --sidebar-primary: #ea580b;
  --sidebar-primary-foreground: #ffffff;
  --sidebar-accent: #ffffff;
  --sidebar-accent-foreground: #ea580b;
  --sidebar-border: #f8f9fb;
  --sidebar-ring: #ea580b;
  background-color: var(--background);
  color: var(--foreground);
}

.dark .jobtracker-dashboard,
.dark
  body:has(.jobtracker-dashboard)
  [data-mobile="true"][data-sidebar="sidebar"],
.dark body:has(.jobtracker-dashboard) [data-slot="dropdown-menu-content"],
.dark body:has(.jobtracker-dashboard) [data-slot="select-content"],
.dark body:has(.jobtracker-dashboard) [data-slot="sheet-content"],
.dark body:has(.jobtracker-dashboard) [data-slot="tooltip-content"] {
  --background: #121212;
  --foreground: oklch(0.985 0 0);
  --card: #1d1d1d;
  --card-foreground: oklch(0.985 0 0);
  --popover: #1d1d1d;
  --popover-foreground: oklch(0.985 0 0);
  --primary: #ea580b;
  --primary-foreground: #ffffff;
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: #ea580b;
  --sidebar: #202124;
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-primary: #ea580b;
  --sidebar-primary-foreground: #ffffff;
  --sidebar-accent: oklch(0.269 0 0);
  --sidebar-accent-foreground: #ea580b;
  --sidebar-border: #303136;
  --sidebar-ring: #ea580b;
}
```

### `src/components/dashboards/jobtracker-dashboard/dashboardView.tsx`

```tsx
import { CustomersContent } from "./components/dashboard/customers-content";
import { JobDetailsContent } from "./components/dashboard/job-details/job-details-content";
import { JobsContent } from "./components/dashboard/jobs-content";
import { DashboardShell } from "./components/layout/dashboard-shell";
import {
  DashboardNavigationProvider,
  useDashboardNavigation,
} from "./components/navigation";

function DashboardRoute() {
  const { pathname } = useDashboardNavigation();

  let content = <CustomersContent />;

  if (pathname.startsWith("/jobs/")) {
    content = <JobDetailsContent />;
  } else if (pathname === "/jobs") {
    content = <JobsContent />;
  }

  return <DashboardShell>{content}</DashboardShell>;
}

export default function DashboardView() {
  return (
    <DashboardNavigationProvider>
      <DashboardRoute />
    </DashboardNavigationProvider>
  );
}
```

### `src/components/dashboards/jobtracker-dashboard/data/index.ts`

```ts
export type Customer = {
  name: string;
  email: string;
  phone: string;
  avatar: string;
};

export type Notification = {
  id: string;
  title: string;
  description: string;
  timestamp: string;
};

export type JobStatus = "pre-construction" | "active" | "complete" | "closed";

export type Job = {
  id: string;
  title: string;
  status: JobStatus;
  customer: {
    name: string;
    avatar: string;
  };
  location: string;
  time: string;
};

export type EstimateLineItem = {
  id: string;
  name: string;
  csiCode?: string;
  description?: string;
  quantity: number;
  unit: string;
  unitCost: number;
  marginPercent: number;
};

export type EstimateSection = {
  id: string;
  name: string;
  items: EstimateLineItem[];
};

export type JobEstimate = {
  jobId: string;
  sections: EstimateSection[];
};

const diceBearAvatar = (seed: string) =>
  `https://api.dicebear.com/10.x/avataaars/svg?seed=${encodeURIComponent(seed)}`;

export const currentUser = {
  name: "Vansh Patel",
  email: "vanshpatel@gmail.com",
  avatar: diceBearAvatar("Vansh Patel"),
};

export const notifications: Notification[] = [
  {
    id: "new-customer",
    title: "New customer added",
    description: "Sofia Martinez joined your customer list.",
    timestamp: "2m ago",
  },
  {
    id: "job-status",
    title: "Job status updated",
    description: "Product Designer moved to the next stage.",
    timestamp: "1h ago",
  },
];

export const customers: Customer[] = [
  {
    name: "Vansh Patel",
    email: "vanshpatel@gmail.com",
    phone: "+1 970-2403456",
    avatar: diceBearAvatar("Vansh Patel"),
  },
  {
    name: "Sofia Martinez",
    email: "sofiamartinez@example.com",
    phone: "+1 202-5550189",
    avatar: diceBearAvatar("Sofia Martinez"),
  },
  {
    name: "Zara Khan",
    email: "zarak@example.com",
    phone: "+1 305-5550191",
    avatar: diceBearAvatar("Zara Khan"),
  },
  {
    name: "Ethan Patel",
    email: "ethanp@example.com",
    phone: "+1 503-5550192",
    avatar: diceBearAvatar("Ethan Patel"),
  },
  {
    name: "Liam Johnson",
    email: "liamj@example.com",
    phone: "+1 415-5550190",
    avatar: diceBearAvatar("Liam Johnson"),
  },
];

export const jobs: Job[] = [
  {
    id: "kitchen-remodel-1",
    title: "Kitchen Remodel",
    status: "pre-construction",
    customer: {
      name: "Vansh P.",
      avatar: diceBearAvatar("Vansh Patel"),
    },
    location: "No 14, Bro, Allan avenue, Lekki county",
    time: "Just Now",
  },
  {
    id: "kitchen-remodel-2",
    title: "Kitchen Remodel",
    status: "pre-construction",
    customer: {
      name: "Vansh P.",
      avatar: diceBearAvatar("Vansh Patel"),
    },
    location: "No 14, Bro, Allan avenue, Lekki county",
    time: "Just Now",
  },
  {
    id: "kitchen-remodel-3",
    title: "Kitchen Remodel",
    status: "pre-construction",
    customer: {
      name: "Vansh P.",
      avatar: diceBearAvatar("Vansh Patel"),
    },
    location: "No 14, Bro, Allan avenue, Lekki county",
    time: "Just Now",
  },
  {
    id: "kitchen-remodel-4",
    title: "Kitchen Remodel",
    status: "active",
    customer: {
      name: "Vansh P.",
      avatar: diceBearAvatar("Vansh Patel"),
    },
    location: "No 14, Bro, Allan avenue, Lekki county",
    time: "Just Now",
  },
];

export const jobEstimates: JobEstimate[] = [
  {
    jobId: "kitchen-remodel-1",
    sections: [
      {
        id: "base",
        name: "Base",
        items: [
          {
            id: "tiles",
            name: "Tiles",
            quantity: 1,
            unit: "LS",
            unitCost: 10,
            marginPercent: 20,
          },
          {
            id: "cement",
            name: "Cement",
            quantity: 1,
            unit: "LS",
            unitCost: 7,
            marginPercent: 10,
          },
        ],
      },
      {
        id: "furniture",
        name: "Furniture",
        items: [
          {
            id: "wood",
            name: "Wood",
            quantity: 1,
            unit: "LS",
            unitCost: 10,
            marginPercent: 20,
          },
        ],
      },
    ],
  },
];
```

### `src/components/dashboards/jobtracker-dashboard/demo.tsx`

```tsx
import "./dashboard.css";

import DashboardView from "./dashboardView";

export default function JobtrackerDashboardDemo() {
  return <DashboardView />;
}
```
