diff --git a/apps/db/supabase/migrations/20260424111249_add_qr_code.sql b/apps/db/supabase/migrations/20260424111249_add_qr_code.sql
new file mode 100644
index 0000000000..701f028022
--- /dev/null
+++ b/apps/db/supabase/migrations/20260424111249_add_qr_code.sql
@@ -0,0 +1,128 @@
+create table "public"."qr_code" (
+ "id" uuid not null default gen_random_uuid(),
+ "short_code" text not null,
+ "target_url" text,
+ "qr_type" character varying,
+ "design_settings" json,
+ "created_at" timestamp without time zone,
+ "scan_count" bigint,
+ "user_id" uuid default gen_random_uuid()
+);
+
+
+alter table "public"."qr_code" enable row level security;
+
+
+ create table "public"."scans" (
+ "id" uuid not null default gen_random_uuid(),
+ "qr_id" uuid not null default auth.uid(),
+ "scanned_at" timestamp without time zone,
+ "device_type" character varying,
+ "country" character varying,
+ "ip_address" text
+ );
+
+
+alter table "public"."scans" enable row level security;
+
+CREATE UNIQUE INDEX qr_code_pkey ON public.qr_code USING btree (id);
+
+CREATE UNIQUE INDEX scans_pkey ON public.scans USING btree (id);
+
+alter table "public"."qr_code" add constraint "qr_code_pkey" PRIMARY KEY using index "qr_code_pkey";
+
+alter table "public"."scans" add constraint "scans_pkey" PRIMARY KEY using index "scans_pkey";
+
+alter table "public"."qr_code" add constraint "qr_code_user_id_fkey" FOREIGN KEY (user_id) REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
+
+alter table "public"."qr_code" validate constraint "qr_code_user_id_fkey";
+
+alter table "public"."scans" add constraint "scans_qr_id_fkey" FOREIGN KEY (qr_id) REFERENCES public.qr_code(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
+
+alter table "public"."scans" validate constraint "scans_qr_id_fkey";
+
+grant delete on table "public"."qr_code" to "anon";
+
+grant insert on table "public"."qr_code" to "anon";
+
+grant references on table "public"."qr_code" to "anon";
+
+grant select on table "public"."qr_code" to "anon";
+
+grant trigger on table "public"."qr_code" to "anon";
+
+grant truncate on table "public"."qr_code" to "anon";
+
+grant update on table "public"."qr_code" to "anon";
+
+grant delete on table "public"."qr_code" to "authenticated";
+
+grant insert on table "public"."qr_code" to "authenticated";
+
+grant references on table "public"."qr_code" to "authenticated";
+
+grant select on table "public"."qr_code" to "authenticated";
+
+grant trigger on table "public"."qr_code" to "authenticated";
+
+grant truncate on table "public"."qr_code" to "authenticated";
+
+grant update on table "public"."qr_code" to "authenticated";
+
+grant delete on table "public"."qr_code" to "service_role";
+
+grant insert on table "public"."qr_code" to "service_role";
+
+grant references on table "public"."qr_code" to "service_role";
+
+grant select on table "public"."qr_code" to "service_role";
+
+grant trigger on table "public"."qr_code" to "service_role";
+
+grant truncate on table "public"."qr_code" to "service_role";
+
+grant update on table "public"."qr_code" to "service_role";
+
+grant delete on table "public"."scans" to "anon";
+
+grant insert on table "public"."scans" to "anon";
+
+grant references on table "public"."scans" to "anon";
+
+grant select on table "public"."scans" to "anon";
+
+grant trigger on table "public"."scans" to "anon";
+
+grant truncate on table "public"."scans" to "anon";
+
+grant update on table "public"."scans" to "anon";
+
+grant delete on table "public"."scans" to "authenticated";
+
+grant insert on table "public"."scans" to "authenticated";
+
+grant references on table "public"."scans" to "authenticated";
+
+grant select on table "public"."scans" to "authenticated";
+
+grant trigger on table "public"."scans" to "authenticated";
+
+grant truncate on table "public"."scans" to "authenticated";
+
+grant update on table "public"."scans" to "authenticated";
+
+grant delete on table "public"."scans" to "service_role";
+
+grant insert on table "public"."scans" to "service_role";
+
+grant references on table "public"."scans" to "service_role";
+
+grant select on table "public"."scans" to "service_role";
+
+grant trigger on table "public"."scans" to "service_role";
+
+grant truncate on table "public"."scans" to "service_role";
+
+grant update on table "public"."scans" to "service_role";
+
+
diff --git a/apps/web/package.json b/apps/web/package.json
index ec8e810c4f..e07eb2783f 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -80,8 +80,10 @@
"octokit": "^5.0.5",
"papaparse": "^5.5.3",
"phaser": "^3.90.0",
+ "qr-code-styling": "^1.9.2",
"qrcode.react": "^4.2.0",
"react": "^19.2.6",
+ "react-color-pikr": "^1.1.2",
"react-confetti": "^6.4.0",
"react-dom": "^19.2.6",
"react-intersection-observer": "^10.0.3",
diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/finance/transactions/[transactionId]/objects.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/finance/transactions/[transactionId]/objects.tsx
index 49082a10fd..e2f1aa0638 100644
--- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/finance/transactions/[transactionId]/objects.tsx
+++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/finance/transactions/[transactionId]/objects.tsx
@@ -1,15 +1,15 @@
'use client';
-import { TransactionObjectRowActions } from './row-actions';
-import { joinPath } from '@/utils/path-helper';
-import { StorageObject } from '@ncthub/types/primitives/StorageObject';
+import type { StorageObject } from '@ncthub/types/primitives/StorageObject';
import { Button } from '@ncthub/ui/button';
import { FileText, LayoutGrid, LayoutList } from '@ncthub/ui/icons';
import { Separator } from '@ncthub/ui/separator';
-import { useTranslations } from 'next-intl';
import Image from 'next/image';
import Link from 'next/link';
+import { useTranslations } from 'next-intl';
import { useState } from 'react';
+import { joinPath } from '@/utils/path-helper';
+import { TransactionObjectRowActions } from './row-actions';
export function DetailObjects({
wsId,
@@ -25,7 +25,7 @@ export function DetailObjects({
return (
-
+
{t('invoices.files')}