feat: establish Stitch cockpit frontend contract

This commit is contained in:
Jens
2026-07-22 19:48:36 +02:00
parent a30ecbc553
commit 09af98193f
17 changed files with 764 additions and 941 deletions
+3
View File
@@ -0,0 +1,3 @@
{% extends "base.html" %}
{% block title %}Geen toegang · VacatureRadar{% endblock %}
{% block content %}<section class="empty-state"><div class="empty-icon"><svg class="icon"><use href="#icon-alert"></use></svg></div><p class="eyebrow">403 · Toegang geweigerd</p><h1>Deze intelligence is niet voor jou beschikbaar</h1><p>Je account heeft geen toegang tot dit onderdeel. Er zijn geen gegevens gewijzigd.</p><a class="button button-primary" href="{% url 'dashboard:today' %}">Terug naar overzicht</a></section>{% endblock %}
+3
View File
@@ -0,0 +1,3 @@
{% extends "base.html" %}
{% block title %}Niet gevonden · VacatureRadar{% endblock %}
{% block content %}<section class="empty-state"><div class="empty-icon"><svg class="icon"><use href="#icon-radar"></use></svg></div><p class="eyebrow">404 · Geen radarsignaal</p><h1>Deze pagina is niet gevonden</h1><p>De link kan verouderd zijn of het object bestaat niet meer.</p><a class="button button-primary" href="{% url 'dashboard:today' %}">Terug naar overzicht</a></section>{% endblock %}
+3
View File
@@ -0,0 +1,3 @@
{% extends "base.html" %}
{% block title %}Systeemfout · VacatureRadar{% endblock %}
{% block content %}<section class="empty-state"><div class="empty-icon"><svg class="icon"><use href="#icon-system"></use></svg></div><p class="eyebrow">500 · Systeemsignaal</p><h1>De radar kon dit verzoek niet verwerken</h1><p>Probeer het later opnieuw. Er worden hier geen technische details of gevoelige gegevens getoond.</p><a class="button button-primary" href="/">Terug naar overzicht</a></section>{% endblock %}
+74 -75
View File
@@ -5,92 +5,91 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark light">
<meta name="theme-color" content="#07101f">
<meta name="theme-color" content="#060e20">
<title>{% block title %}{{ app_name }}{% endblock %}</title>
<link rel="icon" href="{% static 'favicon.svg' %}" type="image/svg+xml">
<link rel="stylesheet" href="{% static 'css/app.css' %}?v={{ app_version }}">
<script src="{% static 'js/app.js' %}?v={{ app_version }}" defer></script>
{% block extra_head %}{% endblock %}
</head>
<body>
<body class="{% block body_class %}{% endblock %}">
{% include "components/icon_sprite.html" %}
<a class="skip-link" href="#main">Naar hoofdinhoud</a>
{% if user.is_authenticated %}
<div class="app-frame" data-app-frame>
<aside class="sidebar" data-sidebar aria-label="Hoofdnavigatie">
<div class="sidebar-brand">
<a class="brand" href="{% url 'dashboard:today' %}">
<span class="brand-mark" aria-hidden="true"><svg class="icon"><use href="#icon-radar"></use></svg></span>
<span><strong>VacatureRadar</strong><small>Intelligence cockpit</small></span>
</a>
<button class="icon-button sidebar-close mobile-only" type="button" data-nav-close aria-label="Menu sluiten">
<svg class="icon"><use href="#icon-close"></use></svg>
</button>
<div class="cockpit" data-app-frame>
<aside class="cockpit-sidebar" data-sidebar aria-label="Hoofdnavigatie">
<div class="sidebar-heading">
<a class="cockpit-brand" href="{% url 'dashboard:today' %}" aria-label="VacatureRadar dashboard">
<span class="brand-radar" aria-hidden="true"><svg class="icon"><use href="#icon-radar"></use></svg></span>
<span><strong>VacatureRadar</strong><small>Intelligence cockpit</small></span>
</a>
<button class="icon-button nav-close mobile-only" type="button" data-nav-close aria-label="Menu sluiten"><svg class="icon"><use href="#icon-close"></use></svg></button>
</div>
<nav class="cockpit-nav">
<p class="nav-caption">Intelligence</p>
<a class="nav-item {% if request.resolver_match.namespace == 'dashboard' and request.resolver_match.url_name == 'today' %}is-active{% endif %}" href="{% url 'dashboard:today' %}"><svg class="icon"><use href="#icon-dashboard"></use></svg><span>Overzicht</span></a>
<a class="nav-item {% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'list' %}is-active{% endif %}" href="{% url 'jobs:list' %}"><svg class="icon"><use href="#icon-radar"></use></svg><span>Nieuwe matches</span></a>
<a class="nav-item {% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'detail' %}is-active{% endif %}" href="{% url 'jobs:list' %}?focus=all"><svg class="icon"><use href="#icon-briefcase"></use></svg><span>Alle vacatures</span></a>
<a class="nav-item {% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'skill-insights' %}is-active{% endif %}" href="{% url 'jobs:skill-insights' %}"><svg class="icon"><use href="#icon-chart"></use></svg><span>Skillsradar</span></a>
<a class="nav-item {% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'applications' or request.resolver_match.url_name == 'application-edit' %}is-active{% endif %}" href="{% url 'jobs:applications' %}"><svg class="icon"><use href="#icon-pipeline"></use></svg><span>Sollicitaties</span></a>
<p class="nav-caption">Radarbeheer</p>
<a class="nav-item {% if request.resolver_match.namespace == 'profiles' %}is-active{% endif %}" href="{% url 'profiles:list' %}"><svg class="icon"><use href="#icon-profile"></use></svg><span>Zoekprofiel</span></a>
<a class="nav-item {% if request.resolver_match.namespace == 'sources' %}is-active{% endif %}" href="{% url 'sources:list' %}"><svg class="icon"><use href="#icon-sources"></use></svg><span>Bronnen</span></a>
<a class="nav-item {% if request.resolver_match.namespace == 'dashboard' and request.resolver_match.url_name == 'system' %}is-active{% endif %}" href="{% url 'dashboard:system' %}"><svg class="icon"><use href="#icon-system"></use></svg><span>Automatisering</span></a>
</nav>
<div class="sidebar-radar-card">
<span class="status-beacon {% if navigation_profile %}is-live{% endif %}" aria-hidden="true"></span>
<span><strong>{% if navigation_profile %}Radar actief{% else %}Profiel nodig{% endif %}</strong><small>{% if navigation_profile %}{{ navigation_profile.name }}{% else %}Configureer je zoekprofiel{% endif %}</small></span>
</div>
<div class="sidebar-account">
<span class="avatar" aria-hidden="true">{{ user.first_name|default:user.username|slice:":1"|upper }}</span>
<span><strong>{{ user.get_full_name|default:user.username }}</strong><small>Persoonlijke radar</small></span>
</div>
</aside>
<button class="nav-scrim" type="button" data-nav-scrim aria-label="Menu sluiten" tabindex="-1"></button>
<div class="cockpit-workspace">
<header class="cockpit-topbar">
<div class="topbar-start">
<button class="icon-button mobile-only" type="button" data-nav-toggle aria-label="Menu openen" aria-expanded="false"><svg class="icon"><use href="#icon-menu"></use></svg></button>
<form class="command-search" method="get" action="{% url 'jobs:list' %}" role="search">
<label class="sr-only" for="global-search">Zoek door vacatures</label>
<svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg>
<input id="global-search" type="search" name="q" value="{% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'list' %}{{ request.GET.q }}{% endif %}" placeholder="Zoek vacatures, werkgevers of skills…">
<kbd aria-hidden="true">/</kbd>
</form>
</div>
<nav class="nav-stack">
<p class="nav-group-label">Radar</p>
<a class="nav-link {% if request.resolver_match.namespace == 'dashboard' and request.resolver_match.url_name == 'today' %}active{% endif %}" href="{% url 'dashboard:today' %}">
<svg class="icon"><use href="#icon-dashboard"></use></svg><span>Vandaag<small>beste matches</small></span>
</a>
<a class="nav-link {% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'list' %}active{% endif %}" href="{% url 'jobs:list' %}">
<svg class="icon"><use href="#icon-search"></use></svg><span>Vacatures<small>zoeken en filteren</small></span>
</a>
<a class="nav-link {% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'skill-insights' %}active{% endif %}" href="{% url 'jobs:skill-insights' %}">
<svg class="icon"><use href="#icon-radar"></use></svg><span>Skillsradar<small>vraag en leerkansen</small></span>
</a>
<a class="nav-link {% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'applications' or request.resolver_match.url_name == 'application-edit' %}active{% endif %}" href="{% url 'jobs:applications' %}">
<svg class="icon"><use href="#icon-pipeline"></use></svg><span>Sollicitaties<small>persoonlijke pipeline</small></span>
</a>
<p class="nav-group-label">Beheer</p>
<a class="nav-link {% if request.resolver_match.namespace == 'profiles' %}active{% endif %}" href="{% url 'profiles:list' %}">
<svg class="icon"><use href="#icon-profile"></use></svg><span>Zoekprofiel<small>regels en voorkeuren</small></span>
</a>
<a class="nav-link {% if request.resolver_match.namespace == 'sources' %}active{% endif %}" href="{% url 'sources:list' %}">
<svg class="icon"><use href="#icon-sources"></use></svg><span>Bronnen<small>import en gezondheid</small></span>
</a>
<a class="nav-link {% if request.resolver_match.namespace == 'dashboard' and request.resolver_match.url_name == 'system' %}active{% endif %}" href="{% url 'dashboard:system' %}">
<svg class="icon"><use href="#icon-system"></use></svg><span>Systeem<small>readiness en beheer</small></span>
</a>
</nav>
<div class="sidebar-user">
<span class="avatar" aria-hidden="true">{{ user.username|slice:":1"|upper }}</span>
<span><strong>{{ user.get_full_name|default:user.username }}</strong><small>Lokale gebruiker</small></span>
<span class="status-dot" title="Aangemeld"></span>
<div class="topbar-actions">
<a class="system-live" href="{% url 'dashboard:system' %}"><span class="status-beacon is-live" aria-hidden="true"></span><span class="desktop-label">Systeemstatus</span></a>
<button class="icon-button" type="button" data-theme-toggle aria-label="Licht thema activeren" title="Thema wisselen"><svg class="icon theme-icon-dark"><use href="#icon-sun"></use></svg><svg class="icon theme-icon-light"><use href="#icon-moon"></use></svg></button>
<details class="account-menu">
<summary aria-label="Gebruikersmenu openen"><span class="avatar avatar-small" aria-hidden="true">{{ user.first_name|default:user.username|slice:":1"|upper }}</span><svg class="icon desktop-label"><use href="#icon-chevron"></use></svg></summary>
<div class="account-popover"><strong>{{ user.get_full_name|default:user.username }}</strong><small>{{ user.username }}</small>{% if navigation_profile %}<a href="{% url 'profiles:edit' navigation_profile.pk %}">Zoekprofiel aanpassen</a>{% endif %}<form action="{% url 'logout' %}" method="post">{% csrf_token %}<button type="submit"><svg class="icon"><use href="#icon-logout"></use></svg>Afmelden</button></form></div>
</details>
</div>
</aside>
<button class="nav-scrim" type="button" data-nav-scrim aria-label="Menu sluiten" tabindex="-1"></button>
<div class="workspace">
<header class="utilitybar">
<div class="utilitybar-start">
<button class="icon-button mobile-only" type="button" data-nav-toggle aria-label="Menu openen" aria-expanded="false">
<svg class="icon"><use href="#icon-menu"></use></svg>
</button>
<form class="global-search" method="get" action="{% url 'jobs:list' %}" role="search">
<label class="sr-only" for="global-search">Zoek door vacatures</label>
<svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg>
<input id="global-search" type="search" name="q" value="{% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'list' %}{{ request.GET.q }}{% endif %}" placeholder="Zoek door vacatures...">
</form>
<div class="utility-title utility-radar"><span class="signal-dot" aria-hidden="true"></span><span>Radar actief</span></div>
</div>
<div class="utility-actions">
<button class="icon-button" type="button" data-theme-toggle aria-label="Thema wisselen" title="Thema wisselen">
<svg class="icon theme-icon-dark"><use href="#icon-sun"></use></svg>
<svg class="icon theme-icon-light"><use href="#icon-moon"></use></svg>
</button>
{% if navigation_profile %}<a class="button button-primary utility-cta" href="{% url 'profiles:edit' navigation_profile.pk %}"><svg class="icon"><use href="#icon-profile"></use></svg><span class="desktop-label">Zoekprofiel aanpassen</span></a>{% endif %}
<form action="{% url 'logout' %}" method="post">{% csrf_token %}<button class="utility-link" type="submit" aria-label="Afmelden"><svg class="icon"><use href="#icon-logout"></use></svg><span class="desktop-label">Afmelden</span></button></form>
</div>
</header>
<main id="main" class="main-content" tabindex="-1">
</header>
<main id="main" class="cockpit-main" tabindex="-1">
{% if messages %}<div class="toast-stack" aria-live="polite" aria-atomic="true">{% for message in messages %}<div class="toast {{ message.tags }}"><svg class="icon" aria-hidden="true"><use href="{% if message.tags == 'error' %}#icon-alert{% else %}#icon-check{% endif %}"></use></svg><span>{{ message }}</span></div>{% endfor %}</div>{% endif %}
{% else %}
<main id="main" class="auth-main" tabindex="-1">
<main id="main" class="auth-shell" tabindex="-1">
{% if messages %}<div class="toast-stack" aria-live="polite">{% for message in messages %}<div class="toast {{ message.tags }}">{{ message }}</div>{% endfor %}</div>{% endif %}
{% endif %}
{% if messages %}
<div class="messages" aria-live="polite" aria-atomic="true">
{% for message in messages %}<div class="message {{ message.tags }}">{{ message }}</div>{% endfor %}
</div>
{% endif %}
{% block content %}{% endblock %}
</main>
{% if user.is_authenticated %}</div></div>{% endif %}
{% block content %}{% endblock %}
</main>
{% if user.is_authenticated %}
<nav class="mobile-tabbar" aria-label="Mobiele hoofdnavigatie">
<a class="{% if request.resolver_match.namespace == 'dashboard' %}is-active{% endif %}" href="{% url 'dashboard:today' %}"><svg class="icon"><use href="#icon-dashboard"></use></svg><span>Overzicht</span></a>
<a class="{% if request.resolver_match.namespace == 'jobs' and request.resolver_match.url_name == 'list' %}is-active{% endif %}" href="{% url 'jobs:list' %}"><svg class="icon"><use href="#icon-radar"></use></svg><span>Matches</span></a>
<a class="{% if request.resolver_match.url_name == 'applications' %}is-active{% endif %}" href="{% url 'jobs:applications' %}"><svg class="icon"><use href="#icon-pipeline"></use></svg><span>Pipeline</span></a>
<a class="{% if request.resolver_match.namespace == 'profiles' %}is-active{% endif %}" href="{% url 'profiles:list' %}"><svg class="icon"><use href="#icon-profile"></use></svg><span>Profiel</span></a>
</nav>
</div>
</div>
{% endif %}
{% block extra_js %}{% endblock %}
</body>
</html>
+8
View File
@@ -21,4 +21,12 @@
<symbol id="icon-external" viewBox="0 0 24 24"><path d="M14 4h6v6M20 4l-9 9"/><path d="M18 13v7H4V6h7"/></symbol>
<symbol id="icon-chevron" viewBox="0 0 24 24"><path d="m7 9 5 5 5-5"/></symbol>
<symbol id="icon-upload" viewBox="0 0 24 24"><path d="M12 16V4M7 9l5-5 5 5M4 20h16"/></symbol>
<symbol id="icon-chart" viewBox="0 0 24 24"><path d="M4 19V9M10 19V4M16 19v-7M22 19V7"/></symbol>
<symbol id="icon-activity" viewBox="0 0 24 24"><path d="M3 12h4l2-6 4 12 2-6h6"/></symbol>
<symbol id="icon-building" viewBox="0 0 24 24"><path d="M4 21V5l8-3v19M12 8h8v13M7 7h2M7 11h2M7 15h2M15 11h2M15 15h2M2 21h20"/></symbol>
<symbol id="icon-filter" viewBox="0 0 24 24"><path d="M3 5h18M6 12h12M10 19h4"/></symbol>
<symbol id="icon-refresh" viewBox="0 0 24 24"><path d="M20 7v5h-5M4 17v-5h5"/><path d="M18.5 9A7 7 0 0 0 6 6.5L4 9M5.5 15A7 7 0 0 0 18 17.5l2-2.5"/></symbol>
<symbol id="icon-list" viewBox="0 0 24 24"><path d="M9 6h12M9 12h12M9 18h12M3 6h.01M3 12h.01M3 18h.01"/></symbol>
<symbol id="icon-calendar" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M8 3v4M16 3v4M3 10h18"/></symbol>
<symbol id="icon-mail" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m4 7 8 6 8-6"/></symbol>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB