Commit Graph

13 Commits

Author SHA1 Message Date
schalli c4db3b2e65 feat(quick-260907-let): Knopf "Verbindung testen" im Postfach-Formular
- testEmailConnection im API-Klienten, POST email-config/test
- EmailAlertConfigForm: Testknopf vor Speichern, Wartezustand, gruene/rote
  Rueckmeldung, Formularaenderung raeumt vorherige Rueckmeldung weg
- Beschreibungsblock der Komponente korrigiert (Knopf existiert jetzt)
- Vier neue Schluessel unter tenderRadar.emailAlerts in de/en
- vi.mock-Fabriken in EmailAlertConfigForm.test.tsx und my-sources.test.tsx
  um testEmailConnection erweitert, zwei neue Testfaelle

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-07 15:43:51 +02:00
schalli 4100bb575e feat(17-03): "Meine Quellen" wird vollstaendig — Postfach, eigene Feeds, Benachrichtigung
- RssFeedSource bekommt isPlatformWide (server-derived), createRssFeed nimmt
  einen scope-Parameter (personal/platform, Vorgabe personal)
- RssFeedListForm bekommt scope-Prop: personal zeigt eigene Feeds editierbar +
  plattformweite als schlichte Aufzaehlung ohne Knoepfe darunter; platform
  zeigt nur plattformweite Feeds editierbar
- DigestIntervalForm aus settings/page.tsx unveraendert herausgeloest (keine
  neuen Beschriftungen, gleiche settings.*-Schluessel)
- my-sources/page.tsx um "Meine Feeds" und "Benachrichtigung" erweitert,
  Verweis auf die Administrationsseite nur fuer ADMIN/SUPER_ADMIN
- settings/page.tsx vorgezogen auf RssFeedListForm scope="platform" (Rule 3,
  eigener Type-Check-Verify sonst rot) — volle Rollenpruesung folgt Task 2

Rule 1: createRssFeed's Antwort traegt kein isPlatformWide (nur GET mappt es
serverseitig) — RssFeedListForm setzt es nach dem Anlegen lokal aus dem
verwendeten scope, sonst wuerde ein frisch angelegter plattformweiter Feed
bis zum naechsten Neuladen aus seiner eigenen Liste verschwinden.
2026-08-12 11:56:42 +02:00
schalli bdaf154f66 feat(260723-lvg): add "Jetzt abrufen" manual poll button to tender radar
PollNowButton sits next to the settings gear in the tender-radar header,
mirrors the DKV spinner/disabled UX, and bumps a refreshKey on success to
refetch ResultsList without touching any filter. Failure surfaces an
i18n error (de/en parity). pollNow() client hits POST
/modules/tender-radar/poll-now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 15:54:43 +02:00
schalli 947325f6ad feat(14-04): CoverageBanner "manuell beobachten" denylist block
- Add DenylistedPortal type + fetchDenylistedPortals() to
  tender-radar-api.ts, following the existing credentials:'include' fetch
  convention
- CoverageBanner fetches the denylisted-portals endpoint on mount and
  renders vergabe24/aumass with direct links (rel="noopener noreferrer",
  target="_blank"); block renders independently of the onlyDoe coverage
  note and fails silently on fetch error
- Add CoverageBanner.test.tsx asserting both portal hrefs, independence
  from the coverage note, and fail-silent behavior

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:59:44 +02:00
schalli 48e12523f3 feat(14-03): add email-config admin routes + D-13 read-side visibility filter + EmailAlertConfigForm
buildTenderWhere gains an optional ownerTenantId param: a resolved
requesting tenant sees global tenders (null) plus its own private ones
(OR[global, mine]); an unresolved requester fails CLOSED to global-only —
never an accidental cross-tenant leak.

TendersController: listTenders/getTender resolve the requesting tenant
leniently from the auth context (resolveRequestingTenantId, never throws)
and apply the D-13 filter; getTender 404s (not a distinct "forbidden") when
a tender's non-null ownerTenantId doesn't match the requester, so no
cross-tenant detail leak. New GET/PUT /modules/tender-radar/email-config
routes (Roles ADMIN/SUPER_ADMIN, tenantId from auth context, never the
body) delegate to TenderEmailConfigService — declared before @Get(':id')
per the project's NestJS route-order convention.

Web: EmailAlertConfig type + fetchEmailConfig/saveEmailConfig client
functions; EmailAlertConfigForm mirrors the DKV InboxConfigForm (password
blank on load, only sent when typed — T-07-12), added as a new
"E-Mail-Alerts" section on the existing tender-radar settings page.
Hardcoded German strings — i18n is Plan 14-05.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:53:17 +02:00
schalli 48a2dc1026 feat(14-02): add RSS feed admin routes, API client, and settings UI
Adds GET/POST/DELETE /modules/tender-radar/rss-feeds (Roles-guarded
ADMIN/SUPER_ADMIN), declared before the existing @Get(':id') handler to
avoid NestJS route-order shadowing. Delegates to
TenderRssFeedSourceService; the denylist/SSRF rejection (D-14) surfaces
as a 400 unchanged.

Web: tender-radar-api.ts gains listRssFeeds/createRssFeed/deleteRssFeed
(relaying the backend's specific rejection message via
extractErrorMessage), and a new RssFeedListForm client component renders
an "RSS-Feeds" section on the tender-radar settings page (D-09) — list,
add (with inline denylist error), and remove global feed URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:29:07 +02:00
schalli bf61316500 feat(13-06): TenderDetail renders all cross-source links
Tender.sources[] added to the API client type (sourcePortal, sourceUrl,
sourceNoticeId). TenderDetail now renders one link per TenderSource
with a German portal label (DÖE/tender24/DTVP/...), falling back to
the existing single sourceUrl block when sources is missing or empty
(older responses, single-source tenders).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:56:06 +02:00
schalli 73a7e49f85 feat(12-04): tender-radar-api client — notification-pref + instantAlert
- fetchNotificationPref/saveNotificationPref for GET/PUT
  /modules/tender-radar/notification-pref (NOTIFY-01)
- SavedSearch/Create/UpdateSavedSearchPayload now carry instantAlert
  (NOTIFY-02, D-04)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 09:28:53 +02:00
schalli dd3ff9ea30 feat(11-06): SavedSearchBar UI — save/load/rename/delete profiles (FILTER-06)
GREEN phase — extends tender-radar-api.ts with listSavedSearches/
createSavedSearch/updateSavedSearch/deleteSavedSearch (plain fetch,
credentials: include), adds SavedSearchBar with the
serializeFiltersFromSearchParams/filtersToSearchParams round-trip helpers
(URL searchParams <-> filters JSON, deliberately excluding page/tender —
navigation state, not filter state), and mounts it above FilterPanel in
page.tsx. Hardcoded German UI per phase convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:50:55 +02:00
schalli 1eb9e4f567 feat(11-05): read/favorite triage toggles + Merklisten-Filter in the UI
Adds fetchTriage()/setTriage() to tender-radar-api.ts (plain fetch,
consistent with the existing client). ResultsList batch-fetches the
current user's triage state for the visible ids and merges it into a
local per-tenderId map; a failed triage fetch never blocks rendering the
list itself. Each row gets a Gelesen/Ungelesen and a Favorit toggle
(optimistic update with revert-on-failure, event.stopPropagation() so the
row's own click-to-open-detail doesn't fire); read rows render dimmed.
FilterPanel gains a "Nur Favoriten/Merkliste" checkbox writing favOnly
into the URL, which ResultsList already forwards generically to the
backend. ResultsList.test.tsx extended (Rule 3 — required to keep the
component test green with the new triage batch call) with coverage for
batch-merge, both toggles, optimistic revert, and graceful degradation
when the triage fetch fails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:37:30 +02:00
schalli 653c63b072 feat(11-04): TenderDetail component with ?tender=<id> wiring
Adds getTender() to the api client and a self-fetching TenderDetail
overlay (pattern: SourceConfigForm) rendering all Tender fields plus a
safe (rel=noopener noreferrer, target=_blank) sourceUrl link. No local
mirroring of Vergabeunterlagen — rawPayload is 100% NULL in the live DB
(research finding), so only the source link exists; NULL value/deadline
render graceful German placeholders (D-05 applies to the detail view too).

page.tsx reads ?tender=<id> via useSearchParams and renders TenderDetail
as an overlay; closing removes the param. ResultsList row clicks set the
param (deviation: ResultsList.tsx was not listed in the plan's
files_modified but is required by the plan's own done-criteria/key_link
"ResultsList-Zeile setzt ?tender=<id>" — Rule 3 auto-fix, blocking).

3/3 TenderDetail tests pass; full web suite (117 tests) and tsc --noEmit
both clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:21:54 +02:00
schalli b55bb55c0e test(11-01): add failing ResultsList spec + extend tender-radar-api client (RED)
Extends tender-radar-api.ts with the Tender type, listTenders(params)
and fetchCoverage() (plain fetch, credentials:'include', matching the
established fetchSourceConfig pattern — TanStack Query is not installed).

Adds the RED-first ResultsList.test.tsx: render items with
title/buyerName/deadline/value, "keine Wertangabe" for null estimatedValue
(D-05), and an empty-state message. ResultsList.tsx does not exist yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:53:04 +02:00
schalli 4360bc0c6b feat(10-06): tender-radar-api client + admin source-config settings form
- tender-radar-api.ts: fetchSourceConfig/saveSourceConfig hitting GET/PUT
  /modules/tender-radar/source-config (Plan 05 endpoint)
- SourceConfigForm: load-on-mount, numeric interval (5-1440 min, mirrors
  backend SourceConfigDto bounds) + isActive toggle, read-only sourceType/
  lastIngestedDay display
- settings/page.tsx: standard App Router route rendering the form,
  no module-loader whitelist change needed
2026-07-21 11:24:32 +02:00