Let the same choice open notes and drafts
The picker in the bell now governs both lists, so note_subscriptions is renamed to colleague_subscriptions -- a name that only mentions notes would mislead the next reader. Reading and writing a draft now reach differently far. hire_drafts_owner (for all) is split into four policies: select lets in your own drafts and those of the people you added, while insert/update/delete stay with the owner. A draft is unfinished work with no lock and no history; two people writing into the same row would overwrite each other silently. That split forces a change in the actions: a policy does not reject a write, it lets it hit no rows. saveHireDraft and deleteHireDraft now read the row count instead of reporting success over a row that never changed. The card shows a foreign draft with its author and without Fortsetzen or Loeschen -- offering a button that reliably ends in a database error is a promise without cover. check-schema-types.mjs learns `alter table ... rename to`; without it the drift check reports one rename as two errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import type { Tx } from "./db";
|
||||
import { jsonArrayFrom, jsonObjectFrom, zeitstempel } from "./db/json";
|
||||
import { jsonArrayFrom, jsonObjectFrom } from "./db/json";
|
||||
import { besetzungenAbfrage, pickPlacements } from "./placement";
|
||||
import { buildOrgMaps, orgMapsAbfragen, type OrgEb } from "./org";
|
||||
import { baueEntwuerfe, entwuerfeAbfrage, type EntwurfZeile } from "./entwuerfe";
|
||||
import { sichtbareNotizen } from "./notes";
|
||||
import { offeneStellenAbfrage, resolveOpenPositions, type OffeneStelle } from "./positions";
|
||||
import type { HistoryEventType } from "./types";
|
||||
@@ -13,6 +14,9 @@ import type { AnstehendArt } from "./dashboard-filter";
|
||||
// das Ergebnis gegen den alten Weg halten lässt, ohne eine React-Komponente
|
||||
// aufzubauen.
|
||||
|
||||
/** Eine gültige uuid, die keiner Zeile gehört — für den abgemeldeten Fall. */
|
||||
const KEINE_KENNUNG = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
export type DashboardParams = {
|
||||
userId: string | null;
|
||||
today: string;
|
||||
@@ -42,14 +46,14 @@ export async function loadDashboardData(tx: Tx, p: DashboardParams) {
|
||||
|
||||
const g = await tx
|
||||
.selectNoFrom((eb) => [
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("hire_drafts")
|
||||
.select(["id", "step", "payload"])
|
||||
.select((x) => zeitstempel(x.ref("updated_at")).as("updated_at"))
|
||||
.where("created_by", "=", userId ?? "")
|
||||
.orderBy("updated_at", "desc")
|
||||
).as("drafts"),
|
||||
// Die eigenen Entwürfe und die der hinzugewählten Kolleg:innen.
|
||||
//
|
||||
// Ohne Anmeldung geht eine Kennung mit, die es nicht gibt: die Spalte
|
||||
// ist vom Typ uuid, und der leere Text, der hier stand, hätte sich
|
||||
// nicht dorthin umwandeln lassen — die Abfrage wäre nicht leer
|
||||
// ausgegangen, sondern gescheitert. Das Ergebnis wird unten ohnehin
|
||||
// verworfen.
|
||||
jsonArrayFrom(entwuerfeAbfrage(eb, userId ?? KEINE_KENNUNG)).as("drafts"),
|
||||
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
@@ -148,7 +152,7 @@ export async function loadDashboardData(tx: Tx, p: DashboardParams) {
|
||||
const orgMaps = buildOrgMaps(g.units as never, g.locations as never);
|
||||
|
||||
return {
|
||||
drafts: userId ? g.drafts : [],
|
||||
drafts: userId ? baueEntwuerfe(g.drafts as EntwurfZeile[], userId) : [],
|
||||
staffRows: g.staffRows,
|
||||
hiresYtd: Number(g.hiresYtd?.anzahl ?? 0),
|
||||
exitsYtd: Number(g.exitsYtd?.anzahl ?? 0),
|
||||
|
||||
Reference in New Issue
Block a user