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:
@@ -33,6 +33,7 @@ interface Quotation {
|
||||
total: number;
|
||||
status: string;
|
||||
order_id?: number;
|
||||
order_status?: string;
|
||||
}
|
||||
|
||||
interface Draft {
|
||||
@@ -118,8 +119,13 @@ export default function Offers() {
|
||||
setDraft(null);
|
||||
};
|
||||
|
||||
const getRowClass = (invalidated: boolean, expired: boolean) => {
|
||||
const getRowClass = (
|
||||
invalidated: boolean,
|
||||
expired: boolean,
|
||||
completed: boolean,
|
||||
) => {
|
||||
if (invalidated) return "offers-invalidated-row";
|
||||
if (completed) return "offers-completed-row";
|
||||
if (expired) return "offers-expired-row";
|
||||
return "";
|
||||
};
|
||||
@@ -140,6 +146,9 @@ export default function Offers() {
|
||||
if (result.success) {
|
||||
alert.success(result.message || "Nabídka byla duplikována");
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["invoices"] });
|
||||
} else {
|
||||
alert.error(result.error || "Nepodařilo se duplikovat nabídku");
|
||||
}
|
||||
@@ -168,10 +177,11 @@ export default function Offers() {
|
||||
if (result.success) {
|
||||
setOrderModal({ show: false, quotation: null });
|
||||
alert.success(result.message || "Objednávka byla vytvořena");
|
||||
navigate(`/orders/${result.data.order_id}`);
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["invoices"] });
|
||||
navigate(`/orders/${result.data.order_id}`);
|
||||
} else {
|
||||
alert.error(result.error || "Nepodařilo se vytvořit objednávku");
|
||||
}
|
||||
@@ -199,6 +209,7 @@ export default function Offers() {
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["invoices"] });
|
||||
} else {
|
||||
alert.error(result.error || "Nepodařilo se smazat nabídku");
|
||||
}
|
||||
@@ -226,6 +237,7 @@ export default function Offers() {
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["invoices"] });
|
||||
} else {
|
||||
alert.error(result.error || "Nepodařilo se zneplatnit nabídku");
|
||||
}
|
||||
@@ -289,7 +301,7 @@ export default function Offers() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="admin-page-actions">
|
||||
{hasPermission("settings.manage") && (
|
||||
{hasPermission("settings.templates") && (
|
||||
<Link
|
||||
to="/offers/templates"
|
||||
className="admin-btn admin-btn-secondary"
|
||||
@@ -520,16 +532,24 @@ export default function Offers() {
|
||||
)}
|
||||
{(quotations as Quotation[]).map((q) => {
|
||||
const isInvalidated = q.status === "invalidated";
|
||||
const isCompleted =
|
||||
!isInvalidated && q.order_status === "dokoncena";
|
||||
const isExpired =
|
||||
!isInvalidated &&
|
||||
!isCompleted &&
|
||||
!q.order_id &&
|
||||
q.valid_until &&
|
||||
new Date(q.valid_until) <
|
||||
new Date(new Date().toDateString());
|
||||
const readOnly = isInvalidated || isCompleted;
|
||||
return (
|
||||
<tr
|
||||
key={q.id}
|
||||
className={getRowClass(isInvalidated, !!isExpired)}
|
||||
className={getRowClass(
|
||||
isInvalidated,
|
||||
!!isExpired,
|
||||
isCompleted,
|
||||
)}
|
||||
>
|
||||
<td>
|
||||
<Link
|
||||
@@ -560,12 +580,10 @@ export default function Offers() {
|
||||
<Link
|
||||
to={`/offers/${q.id}`}
|
||||
className="admin-btn-icon"
|
||||
title={isInvalidated ? "Zobrazit" : "Upravit"}
|
||||
aria-label={
|
||||
isInvalidated ? "Zobrazit" : "Upravit"
|
||||
}
|
||||
title={readOnly ? "Zobrazit" : "Upravit"}
|
||||
aria-label={readOnly ? "Zobrazit" : "Upravit"}
|
||||
>
|
||||
{isInvalidated ? (
|
||||
{readOnly ? (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
@@ -591,35 +609,34 @@ export default function Offers() {
|
||||
</svg>
|
||||
)}
|
||||
</Link>
|
||||
{!isInvalidated &&
|
||||
hasPermission("offers.create") && (
|
||||
<button
|
||||
onClick={() => handleDuplicate(q)}
|
||||
className="admin-btn-icon"
|
||||
title="Duplikovat"
|
||||
disabled={duplicating === q.id}
|
||||
{!readOnly && hasPermission("offers.create") && (
|
||||
<button
|
||||
onClick={() => handleDuplicate(q)}
|
||||
className="admin-btn-icon"
|
||||
title="Duplikovat"
|
||||
disabled={duplicating === q.id}
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<rect
|
||||
x="9"
|
||||
y="9"
|
||||
width="13"
|
||||
height="13"
|
||||
rx="2"
|
||||
ry="2"
|
||||
/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{!isInvalidated && q.order_id ? (
|
||||
<rect
|
||||
x="9"
|
||||
y="9"
|
||||
width="13"
|
||||
height="13"
|
||||
rx="2"
|
||||
ry="2"
|
||||
/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{!readOnly && q.order_id ? (
|
||||
<Link
|
||||
to={`/orders/${q.order_id}`}
|
||||
className="admin-btn-icon accent"
|
||||
@@ -650,7 +667,7 @@ export default function Offers() {
|
||||
</svg>
|
||||
</Link>
|
||||
) : (
|
||||
!isInvalidated &&
|
||||
!readOnly &&
|
||||
hasPermission("orders.create") && (
|
||||
<button
|
||||
onClick={() => {
|
||||
|
||||
Reference in New Issue
Block a user