v1.6.5: fix attendance unique constraint, schema sync, seed script
- Change attendance idx_attendance_user_date from unique to index (allow multiple shifts per day) - Reset migrations to single baseline init migration - Add seed script with admin user (admin/admin) - Update CLAUDE.md with migration workflow documentation - Various frontend fixes (queries, pages, hooks) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,13 @@ import FormField from "../components/FormField";
|
||||
import Forbidden from "../components/Forbidden";
|
||||
import RichEditor from "../components/RichEditor";
|
||||
import useModalLock from "../hooks/useModalLock";
|
||||
import { offerTemplatesOptions } from "../lib/queries/offers";
|
||||
import {
|
||||
itemTemplatesOptions,
|
||||
scopeTemplatesOptions,
|
||||
type ItemTemplate,
|
||||
type ScopeTemplate,
|
||||
type ScopeSection,
|
||||
} from "../lib/queries/offers";
|
||||
import { Skeleton } from "boneyard-js/react";
|
||||
import OffersTemplatesFixture from "../fixtures/OffersTemplatesFixture";
|
||||
|
||||
@@ -16,27 +22,6 @@ import apiFetch from "../utils/api";
|
||||
|
||||
const API_BASE = "/api/admin";
|
||||
|
||||
interface ItemTemplate {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
default_price: number;
|
||||
category: string;
|
||||
}
|
||||
|
||||
interface ScopeSection {
|
||||
_key: string;
|
||||
title: string;
|
||||
title_cz: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface ScopeTemplate {
|
||||
id: number;
|
||||
name: string;
|
||||
sections?: ScopeSection[];
|
||||
}
|
||||
|
||||
interface ItemForm {
|
||||
name: string;
|
||||
description: string;
|
||||
@@ -53,7 +38,7 @@ export default function OffersTemplates() {
|
||||
const { hasPermission } = useAuth();
|
||||
const [activeTab, setActiveTab] = useState<"items" | "scopes">("items");
|
||||
|
||||
if (!hasPermission("settings.manage")) return <Forbidden />;
|
||||
if (!hasPermission("settings.templates")) return <Forbidden />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -96,9 +81,7 @@ export default function OffersTemplates() {
|
||||
function ItemTemplatesTab() {
|
||||
const alert = useAlert();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: templates = [], isPending } = useQuery(
|
||||
offerTemplatesOptions("items"),
|
||||
) as { data: ItemTemplate[]; isPending: boolean };
|
||||
const { data: templates = [], isPending } = useQuery(itemTemplatesOptions());
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [editingTemplate, setEditingTemplate] = useState<ItemTemplate | null>(
|
||||
null,
|
||||
@@ -157,6 +140,7 @@ function ItemTemplatesTab() {
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
alert.success(result.message);
|
||||
queryClient.invalidateQueries({ queryKey: ["offer-templates"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
} else {
|
||||
alert.error(result.error);
|
||||
}
|
||||
@@ -180,6 +164,7 @@ function ItemTemplatesTab() {
|
||||
setDeleteConfirm({ show: false, template: null });
|
||||
alert.success(result.message);
|
||||
queryClient.invalidateQueries({ queryKey: ["offer-templates"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
} else {
|
||||
alert.error(result.error);
|
||||
}
|
||||
@@ -365,7 +350,7 @@ function ItemTemplatesTab() {
|
||||
onChange={(e) =>
|
||||
setForm((p) => ({
|
||||
...p,
|
||||
default_price: e.target.value,
|
||||
default_price: parseFloat(e.target.value),
|
||||
}))
|
||||
}
|
||||
className="admin-form-input"
|
||||
@@ -435,9 +420,7 @@ function ItemTemplatesTab() {
|
||||
function ScopeTemplatesTab() {
|
||||
const alert = useAlert();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: templates = [], isPending } = useQuery(
|
||||
offerTemplatesOptions(),
|
||||
) as { data: ScopeTemplate[]; isPending: boolean };
|
||||
const { data: templates = [], isPending } = useQuery(scopeTemplatesOptions());
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [editingTemplate, setEditingTemplate] = useState<ScopeTemplate | null>(
|
||||
null,
|
||||
@@ -574,6 +557,7 @@ function ScopeTemplatesTab() {
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
alert.success(result.message);
|
||||
queryClient.invalidateQueries({ queryKey: ["offer-templates"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
} else {
|
||||
alert.error(result.error);
|
||||
}
|
||||
@@ -597,6 +581,7 @@ function ScopeTemplatesTab() {
|
||||
setDeleteConfirm({ show: false, template: null });
|
||||
alert.success(result.message);
|
||||
queryClient.invalidateQueries({ queryKey: ["offer-templates"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
} else {
|
||||
alert.error(result.error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user