Phase2: Invoicecomputation/AdminpricingUI/Ainvoicemgnt
Some checks failed
Build and Push / build (push) Failing after 45s

This commit is contained in:
2026-05-24 14:12:26 +02:00
parent cdc2210eaf
commit d4fcc33bc1
4 changed files with 26 additions and 27 deletions

View File

@@ -26,9 +26,16 @@ export async function GET(
if (!pdf) {
return new NextResponse("Not found", { status: 404 });
}
// Construct a response that the browser will render inline (PDF
// viewer) but also offer to download with the right filename.
return new NextResponse(pdf.data, {
// Web `Response`'s `BodyInit` doesn't include Node's `Buffer` — pg
// returns bytea as Buffer but Next/the runtime want a BufferSource.
// Wrap into a zero-copy Uint8Array view (Buffer extends Uint8Array
// under the hood, but TypeScript treats them as distinct).
const body = new Uint8Array(
pdf.data.buffer,
pdf.data.byteOffset,
pdf.data.byteLength
);
return new NextResponse(body, {
status: 200,
headers: {
"Content-Type": "application/pdf",

View File

@@ -301,7 +301,7 @@ function StatusPill({ status }: { status: InvoiceStatus }) {
<span
className={`inline-block px-2 py-0.5 rounded text-xs font-medium ${color}`}
>
{t(`status_${status}` as any)}
{t(`status_${status}`)}
</span>
);
}

View File

@@ -73,7 +73,7 @@ export function InvoicesTable({ initialInvoices }: Props) {
>
{STATUS_FILTERS.map((s) => (
<option key={s} value={s}>
{s === "all" ? t("allStatuses") : t(`status_${s}` as any)}
{s === "all" ? t("allStatuses") : t(`status_${s}`)}
</option>
))}
</select>
@@ -177,7 +177,7 @@ function StatusPill({ status }: { status: InvoiceStatus }) {
<span
className={`inline-block px-2 py-0.5 rounded text-xs font-medium ${color}`}
>
{t(`status_${status}` as any)}
{t(`status_${status}`)}
</span>
);
}

View File

@@ -88,22 +88,17 @@ export function PricingEditor({
const [addingSkill, setAddingSkill] = useState(false);
const [skillError, setSkillError] = useState("");
const addOrUpdateSkill = async (
e: React.FormEvent,
overrideId?: string,
overridePrice?: string
) => {
e.preventDefault();
// Core upsert — used by both the "add new skill" form and the inline
// editor on existing rows. Kept event-free so callers can invoke it
// without synthesizing a fake form event.
const upsertSkillPrice = async (skillId: string, dailyPriceChf: number) => {
setAddingSkill(true);
setSkillError("");
try {
const res = await fetch("/api/admin/billing/skill-pricing", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
skillId: overrideId ?? newSkillId,
dailyPriceChf: Number(overridePrice ?? newSkillPrice),
}),
body: JSON.stringify({ skillId, dailyPriceChf }),
});
if (!res.ok) {
const j = await res.json().catch(() => ({}));
@@ -117,6 +112,12 @@ export function PricingEditor({
}
};
const onAddNewSkill = (e: React.FormEvent) => {
e.preventDefault();
if (!newSkillId) return;
void upsertSkillPrice(newSkillId, Number(newSkillPrice));
};
const deleteSkill = async (skillId: string) => {
if (!confirm(t("confirmDeleteSkillPrice", { skill: skillId }))) return;
setSkillError("");
@@ -254,13 +255,7 @@ export function PricingEditor({
<InlinePriceEditor
skillId={sp.skillId}
initialPrice={sp.dailyPriceChf}
onSave={(price) =>
addOrUpdateSkill(
new Event("submit") as any,
sp.skillId,
String(price)
)
}
onSave={(price) => upsertSkillPrice(sp.skillId, price)}
/>
</td>
<td className="py-2 text-right">
@@ -280,10 +275,7 @@ export function PricingEditor({
<p className="text-sm text-text-muted italic mb-4">{t("noSkillsPriced")}</p>
)}
<form
onSubmit={(e) => addOrUpdateSkill(e)}
className="flex items-end gap-3"
>
<form onSubmit={onAddNewSkill} className="flex items-end gap-3">
<label className="flex-grow">
<span className="text-xs text-text-muted">{t("addSkillLabel")}</span>
<select