Bewerbungen favorisieren (statt Jobangebote)
Stern zum Merken einzelner Bewerbungen auf der Startseiten-Liste, der Bewerbungsdetailseite und einer neuen 'Gemerkte Bewerbungen'-Sektion ganz oben — unabhaengig vom Status. Die bisherigen Jobangebot-Favoriten werden durchgaengig entfernt (Endpoint, Migration, UI, Startseiten-Sektion), da nur Bewerbungen gemerkt werden sollen. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1441,10 +1441,12 @@ async function initializeDatabase() {
|
|||||||
// Favoriten-Merkmal (Stern): 0 = nicht gemerkt, 1 = favorisiert. Favoriten
|
// Favoriten-Merkmal (Stern): 0 = nicht gemerkt, 1 = favorisiert. Favoriten
|
||||||
// erscheinen oben auf der Startseite — unabhängig vom Status (offen /
|
// erscheinen oben auf der Startseite — unabhängig vom Status (offen /
|
||||||
// uebernommen / abgelehnt). Spalte wird nur bei Bedarf angelegt.
|
// uebernommen / abgelehnt). Spalte wird nur bei Bedarf angelegt.
|
||||||
|
// Favoriten-Markierung für Bewerbungen. Erlaubt das Merken einzelner
|
||||||
|
// Bewerbungen, unabhängig von ihrem Status.
|
||||||
{
|
{
|
||||||
const jobSpalten = await dbAll('PRAGMA table_info(jobangebote)');
|
const bewSpalten = await dbAll('PRAGMA table_info(bewerbungen)');
|
||||||
if (!jobSpalten.some((s) => s.name === 'favorit')) {
|
if (!bewSpalten.some((s) => s.name === 'favorit')) {
|
||||||
await exec('ALTER TABLE jobangebote ADD COLUMN favorit INTEGER NOT NULL DEFAULT 0');
|
await exec('ALTER TABLE bewerbungen ADD COLUMN favorit INTEGER NOT NULL DEFAULT 0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1754,14 +1756,13 @@ initializeDatabase().then(async () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gemerkte Stellen (Favoriten) für die Sektion ganz oben auf der
|
// Gemerkte Bewerbungen — unabhängig vom Bewerbungs-Status, ganz oben auf
|
||||||
// Startseite — unabhängig vom Angebot-Status (offen/uebernommen/…).
|
// der Startseite.
|
||||||
const favoriten = await dbAll(
|
const bewerbungFavoriten = await dbAll(
|
||||||
`SELECT j.id, j.firma, j.stelle, j.ort, j.status, j.quelle_url,
|
`SELECT b.id, b.firma, b.stelle, b.ort, b.status, b.datum, b.favorit
|
||||||
j.verknuepfte_bewerbung_id, j.gehalt, j.favorit
|
FROM bewerbungen b
|
||||||
FROM jobangebote j
|
WHERE b.user_id = ? AND b.favorit = 1
|
||||||
WHERE j.user_id = ? AND j.favorit = 1
|
ORDER BY b.updated_at DESC, b.id DESC`,
|
||||||
ORDER BY j.updated_at DESC, j.id DESC`,
|
|
||||||
[U]
|
[U]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1782,7 +1783,7 @@ initializeDatabase().then(async () => {
|
|||||||
statusOptions: STATUS_OPTIONS,
|
statusOptions: STATUS_OPTIONS,
|
||||||
labelOptions: LABEL_OPTIONS,
|
labelOptions: LABEL_OPTIONS,
|
||||||
setupWizard,
|
setupWizard,
|
||||||
favoriten
|
bewerbungFavoriten
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
@@ -3624,18 +3625,16 @@ initializeDatabase().then(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Favorit umschalten (Stern). JSON-Antwort, damit der Client den Stern
|
// Bewerbung als Favorit merken/entmerken — unabhängig vom Bewerbungs-Status.
|
||||||
// sofort umschalten kann, ohne die Seite neu zu laden. Favoriten sind
|
app.post('/api/bewerbungen/:id/favorit', async (req, res) => {
|
||||||
// unabhängig vom Angebot-Status sichtbar — auch „uebernommen“ bleibt merkbar.
|
|
||||||
app.post('/jobangebote/:id/favorit', async (req, res) => {
|
|
||||||
try {
|
try {
|
||||||
const angebot = await dbGet('SELECT favorit FROM jobangebote WHERE id = ? AND user_id = ?', [req.params.id, uid()]);
|
const bewerbung = await dbGet('SELECT favorit FROM bewerbungen WHERE id = ? AND user_id = ?', [req.params.id, uid()]);
|
||||||
if (!angebot) return res.status(404).json({ error: 'Jobangebot nicht gefunden' });
|
if (!bewerbung) return res.status(404).json({ error: 'Bewerbung nicht gefunden' });
|
||||||
const neu = angebot.favorit ? 0 : 1;
|
const neu = bewerbung.favorit ? 0 : 1;
|
||||||
await dbRun('UPDATE jobangebote SET favorit = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND user_id = ?', [neu, req.params.id, uid()]);
|
await dbRun('UPDATE bewerbungen SET favorit = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND user_id = ?', [neu, req.params.id, uid()]);
|
||||||
res.json({ success: true, favorit: neu });
|
res.json({ success: true, favorit: neu });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error toggling favorite:', error);
|
console.error('Error toggling bewerbung favorite:', error);
|
||||||
res.status(500).json({ error: 'Serverfehler' });
|
res.status(500).json({ error: 'Serverfehler' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<span class="px-3 py-1 rounded-full text-sm font-medium <%= statusColor[aktuellerStatus] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>">
|
<span class="px-3 py-1 rounded-full text-sm font-medium <%= statusColor[aktuellerStatus] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>">
|
||||||
<%= aktuellerStatus %>
|
<%= aktuellerStatus %>
|
||||||
</span>
|
</span>
|
||||||
|
<%- include('partials/favorit-button', { j: application, endpoint: '/api/bewerbungen/' + application.id + '/favorit' }) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Application data -->
|
<!-- Application data -->
|
||||||
@@ -1022,5 +1023,7 @@
|
|||||||
}));
|
}));
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<%- include('partials/favorit-script') %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+4
-1
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<main class="flex-1 container mx-auto px-4 py-8">
|
<main class="flex-1 container mx-auto px-4 py-8">
|
||||||
<% var zeigeDashboard = !(typeof setupWizard !== 'undefined' && setupWizard && setupWizard.aktiv); %>
|
<% var zeigeDashboard = !(typeof setupWizard !== 'undefined' && setupWizard && setupWizard.aktiv); %>
|
||||||
<%- include('partials/favoriten') %>
|
<%- include('partials/bewerbung-favoriten') %>
|
||||||
<% if (!zeigeDashboard) { %>
|
<% if (!zeigeDashboard) { %>
|
||||||
<%- include('partials/einrichtung') %>
|
<%- include('partials/einrichtung') %>
|
||||||
<% } %>
|
<% } %>
|
||||||
@@ -252,6 +252,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="app-actions flex items-center gap-3">
|
<span class="app-actions flex items-center gap-3">
|
||||||
|
<%- include('partials/favorit-button', { j: app, endpoint: '/api/bewerbungen/' + app.id + '/favorit', compact: true }) %>
|
||||||
<a href="/bewerbung/<%= app.id %>"
|
<a href="/bewerbung/<%= app.id %>"
|
||||||
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
aria-label="Bearbeiten">
|
aria-label="Bearbeiten">
|
||||||
@@ -666,6 +667,8 @@
|
|||||||
|
|
||||||
<%- include('partials/footer') %>
|
<%- include('partials/footer') %>
|
||||||
|
|
||||||
|
<%- include('partials/favorit-script') %>
|
||||||
|
|
||||||
<!-- Add/Edit Application Modal -->
|
<!-- Add/Edit Application Modal -->
|
||||||
<div id="applicationModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
|
<div id="applicationModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
|
||||||
<div class="flex items-center justify-center min-h-screen p-4">
|
<div class="flex items-center justify-center min-h-screen p-4">
|
||||||
|
|||||||
@@ -139,7 +139,6 @@
|
|||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="shrink-0 flex flex-col gap-2 self-start">
|
<div class="shrink-0 flex flex-col gap-2 self-start">
|
||||||
<%- include('partials/favorit-button', { j: j }) %>
|
|
||||||
<% if (j.verknuepfte_bewerbung_id) { %>
|
<% if (j.verknuepfte_bewerbung_id) { %>
|
||||||
<a href="/bewerbung/<%= j.verknuepfte_bewerbung_id %>"
|
<a href="/bewerbung/<%= j.verknuepfte_bewerbung_id %>"
|
||||||
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||||||
@@ -298,7 +297,5 @@
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<%- include('partials/favorit-script') %>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -100,7 +100,6 @@
|
|||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="shrink-0 flex flex-col gap-2 self-start">
|
<div class="shrink-0 flex flex-col gap-2 self-start">
|
||||||
<%- include('partials/favorit-button', { j: j }) %>
|
|
||||||
<% if (j.verknuepfte_bewerbung_id) { %>
|
<% if (j.verknuepfte_bewerbung_id) { %>
|
||||||
<a href="/bewerbung/<%= j.verknuepfte_bewerbung_id %>"
|
<a href="/bewerbung/<%= j.verknuepfte_bewerbung_id %>"
|
||||||
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||||||
@@ -141,7 +140,5 @@
|
|||||||
if (cy) cy.textContent = new Date().getFullYear();
|
if (cy) cy.textContent = new Date().getFullYear();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<%- include('partials/favorit-script') %>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<%
|
||||||
|
// Gemerkte Bewerbungen — Sektion ganz oben auf der Startseite. Zeigt alle
|
||||||
|
// favorisierten Bewerbungen unabhängig von ihrem Status. Wird nur gerendert,
|
||||||
|
// wenn mindestens ein Favorit vorhanden ist.
|
||||||
|
%>
|
||||||
|
<% if (bewerbungFavoriten && bewerbungFavoriten.length) { %>
|
||||||
|
<%- include('favorit-script') %>
|
||||||
|
<section id="bewerbungFavoritenCard" data-fav-card class="bg-white dark:bg-gray-800 rounded-lg shadow-md mb-8 overflow-hidden">
|
||||||
|
<div class="flex flex-wrap items-center gap-2 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<svg class="h-5 w-5 text-amber-500" viewBox="0 0 24 24" fill="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.5l2.6 5.27 5.82.85-4.21 4.1.99 5.79-5.2-2.73-5.2 2.73.99-5.79-4.21-4.1 5.82-.85 2.6-5.27z"/></svg>
|
||||||
|
<h2 class="text-lg font-semibold text-gray-800 dark:text-white">Gemerkte Bewerbungen</h2>
|
||||||
|
<span class="ml-auto inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-xs font-medium text-amber-700 dark:bg-amber-500/10 dark:text-amber-300">
|
||||||
|
<%= bewerbungFavoriten.length %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul class="divide-y divide-gray-100 dark:divide-gray-700/60">
|
||||||
|
<% bewerbungFavoriten.forEach(function(b){ %>
|
||||||
|
<li data-fav-item class="flex flex-wrap items-start justify-between gap-3 px-6 py-4">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="flex flex-wrap items-center gap-2 mb-1">
|
||||||
|
<% if (b.status) { %>
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-200"><%= b.status %></span>
|
||||||
|
<% } %>
|
||||||
|
<% if (b.datum) { %>
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"><%= new Date(b.datum).toLocaleDateString('de-DE') %></span>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<a href="/bewerbung/<%= b.id %>" class="text-base font-semibold text-gray-800 dark:text-gray-100 break-words hover:underline">
|
||||||
|
<%= b.firma %>
|
||||||
|
</a>
|
||||||
|
<p class="text-sm text-gray-700 dark:text-gray-200 break-words"><%= b.stelle %></p>
|
||||||
|
<% if (b.ort) { %>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5"><%= b.ort %></p>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
<div class="shrink-0 flex flex-col gap-2 self-start items-end">
|
||||||
|
<a href="/bewerbung/<%= b.id %>"
|
||||||
|
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||||||
|
Öffnen
|
||||||
|
</a>
|
||||||
|
<%- include('favorit-button', { j: b, endpoint: '/api/bewerbungen/' + b.id + '/favorit' }) %>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<% }); %>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<% } %>
|
||||||
@@ -1,16 +1,25 @@
|
|||||||
<%
|
<%
|
||||||
// Stern-Button zum Merken/Entfernen eines Jobangebots. Lokale `j` muss das
|
// Stern-Button zum Merken/Entmerken. Generisch einsetzbar für Jobangebote
|
||||||
// Angebot (mindestens id + favorit) sein. Zustand wird über die Klasse
|
// (Standard-Endpunkt /jobangebote/:id/favorit) und Bewerbungen — dann `endpoint`
|
||||||
// .is-fav gesteuert (definiert in partials/favorit-script). Das Skript dort
|
// mitgeben. Entweder `j` (legacy, braucht id + favorit) oder direkt `id`/`favorit`.
|
||||||
// bedient Klicks per Delegation — also nur einmal pro Seite einbinden.
|
// Zustand wird über die Klasse .is-fav gesteuert (definiert in favorit-script).
|
||||||
var favAktiv = !!(typeof j !== 'undefined' && j && j.favorit);
|
// Mit `compact` wird ein reines Icon-Button (für Listenzeilen). Das Skript aus
|
||||||
|
// partials/favorit-script bedient Klicks per Delegation — nur einmal pro Seite
|
||||||
|
// einbinden.
|
||||||
|
var favItem = (typeof item !== 'undefined' && item) ? item : (typeof j !== 'undefined' ? j : null);
|
||||||
|
var favId = (typeof id !== 'undefined' && id != null) ? id : (favItem && favItem.id);
|
||||||
|
var favAktiv = !!(typeof favorit !== 'undefined' ? favorit : (favItem && favItem.favorit));
|
||||||
|
var favEndpoint = (typeof endpoint !== 'undefined' && endpoint)
|
||||||
|
? endpoint
|
||||||
|
: ('/jobangebote/' + encodeURIComponent(favId) + '/favorit');
|
||||||
|
var favCompact = !!(typeof compact !== 'undefined' && compact);
|
||||||
%>
|
%>
|
||||||
<button type="button" class="fav-star<%= favAktiv ? ' is-fav' : '' %> inline-flex items-center justify-center gap-1.5 rounded-md border px-3 py-1.5 text-xs font-medium transition-colors"
|
<button type="button" class="fav-star<%= favAktiv ? ' is-fav' : '' %> inline-flex items-center justify-center gap-1.5 rounded-md border <%= favCompact ? 'p-1.5' : 'px-3 py-1.5' %> text-xs font-medium transition-colors"
|
||||||
data-favorit="<%= j.id %>" data-aktiv="<%= favAktiv ? 1 : 0 %>"
|
data-favorit="<%= favId %>" data-aktiv="<%= favAktiv ? 1 : 0 %>" data-endpoint="<%= favEndpoint %>"
|
||||||
title="<%= favAktiv ? 'Favorit entfernen' : 'Als Favorit merken' %>"
|
title="<%= favAktiv ? 'Favorit entfernen' : 'Als Favorit merken' %>"
|
||||||
aria-pressed="<%= favAktiv ? 'true' : 'false' %>">
|
aria-pressed="<%= favAktiv ? 'true' : 'false' %>">
|
||||||
<svg class="fav-ico h-4 w-4" viewBox="0 0 24 24" fill="<%= favAktiv ? 'currentColor' : 'none' %>" stroke="currentColor" stroke-width="2">
|
<svg class="fav-ico h-4 w-4" viewBox="0 0 24 24" fill="<%= favAktiv ? 'currentColor' : 'none' %>" stroke="currentColor" stroke-width="2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.5l2.6 5.27 5.82.85-4.21 4.1.99 5.79-5.2-2.73-5.2 2.73.99-5.79-4.21-4.1 5.82-.85 2.6-5.27z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.5l2.6 5.27 5.82.85-4.21 4.1.99 5.79-5.2-2.73-5.2 2.73.99-5.79-4.21-4.1 5.82-.85 2.6-5.27z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="fav-label"><%= favAktiv ? 'Gemerkt' : 'Merken' %></span>
|
<% if (!favCompact) { %><span class="fav-label"><%= favAktiv ? 'Gemerkt' : 'Merken' %></span><% } else { %><span class="fav-label hidden"><%= favAktiv ? 'Gemerkt' : 'Merken' %></span><% } %>
|
||||||
</button>
|
</button>
|
||||||
@@ -27,11 +27,12 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var id = btn.getAttribute('data-favorit');
|
var id = btn.getAttribute('data-favorit');
|
||||||
var aktiv = btn.getAttribute('data-aktiv') === '1';
|
var aktiv = btn.getAttribute('data-aktiv') === '1';
|
||||||
|
var endpoint = btn.getAttribute('data-endpoint') || ('/jobangebote/' + encodeURIComponent(id) + '/favorit');
|
||||||
|
|
||||||
// Optimistisch umschalten, sofort sichtbar.
|
// Optimistisch umschalten, sofort sichtbar.
|
||||||
setzeBtn(btn, !aktiv);
|
setzeBtn(btn, !aktiv);
|
||||||
|
|
||||||
fetch('/jobangebote/' + encodeURIComponent(id) + '/favorit', {
|
fetch(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'X-Requested-With': 'fetch' }
|
headers: { 'X-Requested-With': 'fetch' }
|
||||||
})
|
})
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
function entferneFavoritZeile(btn) {
|
function entferneFavoritZeile(btn) {
|
||||||
var item = btn.closest('[data-fav-item]');
|
var item = btn.closest('[data-fav-item]');
|
||||||
if (!item) return; // nicht auf der Startseite -> nur optisch umschalten
|
if (!item) return; // nicht auf der Startseite -> nur optisch umschalten
|
||||||
var card = document.getElementById('favoritenCard');
|
var card = btn.closest('[data-fav-card]') || document.getElementById('favoritenCard');
|
||||||
item.remove();
|
item.remove();
|
||||||
if (card && !card.querySelectorAll('[data-fav-item]').length) {
|
if (card && !card.querySelectorAll('[data-fav-item]').length) {
|
||||||
card.style.transition = 'opacity 0.2s ease';
|
card.style.transition = 'opacity 0.2s ease';
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
<%
|
|
||||||
// Gemerkte Stellen — Sektion ganz oben auf der Startseite. Zeigt alle
|
|
||||||
// favorisierten Jobangebote unabhängig von ihrem Status (offen/uebernommen/…).
|
|
||||||
// Wird nur gerendert, wenn mindestens ein Favorit vorhanden ist.
|
|
||||||
function favStatusBadge(status) {
|
|
||||||
if (status === 'offen') return 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-200';
|
|
||||||
if (status === 'uebernommen') return 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-200';
|
|
||||||
if (status === 'abgelehnt') return 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-200';
|
|
||||||
return 'bg-gray-200 text-gray-700 dark:bg-gray-600 dark:text-gray-200';
|
|
||||||
}
|
|
||||||
function favStatusLabel(status) {
|
|
||||||
if (status === 'offen') return 'Offen';
|
|
||||||
if (status === 'uebernommen') return 'Übernommen';
|
|
||||||
if (status === 'abgelehnt') return 'Abgelehnt';
|
|
||||||
return status || '—';
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
<% if (favoriten && favoriten.length) { %>
|
|
||||||
<%- include('favorit-script') %>
|
|
||||||
<section id="favoritenCard" class="bg-white dark:bg-gray-800 rounded-lg shadow-md mb-8 overflow-hidden">
|
|
||||||
<div class="flex flex-wrap items-center gap-2 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<svg class="h-5 w-5 text-amber-500" viewBox="0 0 24 24" fill="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.5l2.6 5.27 5.82.85-4.21 4.1.99 5.79-5.2-2.73-5.2 2.73.99-5.79-4.21-4.1 5.82-.85 2.6-5.27z"/></svg>
|
|
||||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white">Gemerkte Stellen</h2>
|
|
||||||
<span class="ml-auto inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-xs font-medium text-amber-700 dark:bg-amber-500/10 dark:text-amber-300">
|
|
||||||
<%= favoriten.length %>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ul class="divide-y divide-gray-100 dark:divide-gray-700/60">
|
|
||||||
<% favoriten.forEach(function(j){ %>
|
|
||||||
<li data-fav-item class="flex flex-wrap items-start justify-between gap-3 px-6 py-4">
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<div class="flex flex-wrap items-center gap-2 mb-1">
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= favStatusBadge(j.status) %>"><%= favStatusLabel(j.status) %></span>
|
|
||||||
<% if (j.gehalt) { %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"><%= j.gehalt %></span>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
|
||||||
<% if (j.quelle_url) { %>
|
|
||||||
<a href="<%= safeUrl(j.quelle_url) %>" target="_blank" rel="noopener noreferrer"
|
|
||||||
class="text-base font-semibold text-gray-800 dark:text-gray-100 break-words hover:underline">
|
|
||||||
<%= j.firma %>
|
|
||||||
</a>
|
|
||||||
<% } else { %>
|
|
||||||
<p class="text-base font-semibold text-gray-800 dark:text-gray-100 break-words"><%= j.firma %></p>
|
|
||||||
<% } %>
|
|
||||||
<p class="text-sm text-gray-700 dark:text-gray-200 break-words"><%= j.stelle %></p>
|
|
||||||
<% if (j.ort) { %>
|
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5"><%= j.ort %></p>
|
|
||||||
<% } %>
|
|
||||||
<% if (j.verknuepfte_bewerbung_id) { %>
|
|
||||||
<p class="mt-1.5">
|
|
||||||
<a href="/bewerbung/<%= j.verknuepfte_bewerbung_id %>" class="inline-flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline">
|
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
||||||
Zur Bewerbung
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
|
||||||
<div class="shrink-0 flex flex-col gap-2 self-start items-end">
|
|
||||||
<% if (j.verknuepfte_bewerbung_id) { %>
|
|
||||||
<a href="/bewerbung/<%= j.verknuepfte_bewerbung_id %>"
|
|
||||||
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
|
||||||
Bewerbung
|
|
||||||
</a>
|
|
||||||
<% } %>
|
|
||||||
<%- include('favorit-button', { j: j }) %>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<% } %>
|
|
||||||
Reference in New Issue
Block a user