Skip to content

Commit 2cff86a

Browse files
committed
job-offer-preview-dialog: add new dialog
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent 0182292 commit 2cff86a

4 files changed

Lines changed: 342 additions & 2 deletions

File tree

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
import {css, html} from 'lit';
2+
import * as commonUtils from '@dbp-toolkit/common/utils';
3+
import * as commonStyles from '@dbp-toolkit/common/src/styles.js';
4+
import {ScopedElementsMixin} from '@dbp-toolkit/common/src/scoped/ScopedElementsMixin.js';
5+
import {Icon, Modal} from '@dbp-toolkit/common';
6+
import DBPBulletinLitElement from './dbp-bulletin-lit-element.js';
7+
import {JobOfferDialog} from './dbp-bulletin-job-offer-dialog.js';
8+
9+
export class JobOfferPreviewDialog extends ScopedElementsMixin(DBPBulletinLitElement) {
10+
static get scopedElements() {
11+
return {
12+
'dbp-icon': Icon,
13+
'dbp-modal': Modal,
14+
'dbp-bulletin-job-offer-dialog': JobOfferDialog,
15+
};
16+
}
17+
18+
constructor() {
19+
super();
20+
/** @type {object|null} The job offer currently being previewed */
21+
this.job = null;
22+
}
23+
24+
static get properties() {
25+
return {
26+
...super.properties,
27+
job: {state: true},
28+
};
29+
}
30+
31+
/**
32+
* Opens the preview dialog with the given job offer.
33+
* @param {object} job
34+
*/
35+
open(job) {
36+
this.job = job;
37+
const modal = this._('dbp-modal');
38+
if (modal) {
39+
modal.open();
40+
}
41+
}
42+
43+
/** Closes the preview dialog. */
44+
close() {
45+
const modal = this._('dbp-modal');
46+
if (modal) {
47+
modal.close();
48+
}
49+
}
50+
51+
/** Opens the edit dialog pre-filled with the current job. */
52+
_onEdit() {
53+
const editDialog = this._('dbp-bulletin-job-offer-dialog');
54+
if (editDialog) {
55+
editDialog.open(this.job);
56+
}
57+
}
58+
59+
/**
60+
* Formats an ISO date string (YYYY-MM-DD) to a locale-aware display string.
61+
* Falls back to the raw value when parsing fails.
62+
* @param {string} dateStr
63+
* @returns {string}
64+
*/
65+
_formatDate(dateStr) {
66+
if (!dateStr) return '';
67+
try {
68+
const date = new Date(dateStr);
69+
return date.toLocaleDateString(this.lang === 'de' ? 'de-AT' : 'en-GB');
70+
} catch {
71+
return dateStr;
72+
}
73+
}
74+
75+
render() {
76+
const i18n = this._i18n;
77+
const t = (key) => (i18n ? i18n.t(key) : key);
78+
const job = this.job;
79+
80+
return html`
81+
<dbp-modal
82+
modal-id="job-offer-preview-dialog"
83+
lang="${this.lang}"
84+
style="--dbp-modal-min-width: min(95vw, 760px); --dbp-modal-max-width: min(95vw, 760px); --dbp-modal-max-height: 90vh; --dbp-modal-content-overflow-y: auto;">
85+
<div slot="title">
86+
<h3 class="preview-title">${job ? job.title : ''}</h3>
87+
</div>
88+
89+
<div slot="content" class="preview-content">
90+
${job
91+
? html`
92+
<!-- Meta information row -->
93+
<div class="meta-row">
94+
<div class="meta-fields">
95+
<p class="meta-item">
96+
<strong>${t('job-offer-preview.published-at')}:</strong>
97+
${this._formatDate(job.publishedAt)}
98+
</p>
99+
<p class="meta-item">
100+
<strong>${t('job-offer-preview.deadline')}:</strong>
101+
${this._formatDate(job.deadline)}
102+
</p>
103+
<p class="meta-item">
104+
<strong>${t('job-offer-preview.start-date')}:</strong>
105+
${job.startDate ?? ''}
106+
</p>
107+
<p class="meta-item">
108+
<strong>${t('job-offer-preview.weekly-hours')}:</strong>
109+
${job.weeklyHours ?? ''}
110+
</p>
111+
<p class="meta-item">
112+
<strong>${t('job-offer-preview.organization')}:</strong>
113+
${job.organization ?? ''}
114+
</p>
115+
</div>
116+
${job.areaOfInterest
117+
? html`
118+
<span class="area-badge">${job.areaOfInterest}</span>
119+
`
120+
: ''}
121+
</div>
122+
123+
<!-- Action buttons -->
124+
<div class="action-buttons">
125+
<button
126+
class="button is-secondary action-btn"
127+
type="button"
128+
@click="${this._onEdit}">
129+
<dbp-icon
130+
class="btn-icon"
131+
name="pencil"
132+
aria-hidden="true"></dbp-icon>
133+
${t('job-offer-preview.edit')}
134+
</button>
135+
<button
136+
class="button is-secondary action-btn"
137+
type="button"
138+
@click="${() =>
139+
console.log('Delete job offer:', job.identifier)}">
140+
<dbp-icon
141+
class="btn-icon"
142+
name="trash"
143+
aria-hidden="true"></dbp-icon>
144+
${t('job-offer-preview.delete')}
145+
</button>
146+
<button
147+
class="button is-primary action-btn"
148+
type="button"
149+
@click="${() =>
150+
console.log('View applications for:', job.identifier)}">
151+
<dbp-icon
152+
class="btn-icon"
153+
name="person"
154+
aria-hidden="true"></dbp-icon>
155+
${t('job-offer-preview.view-applications')}
156+
</button>
157+
</div>
158+
159+
<!-- Description -->
160+
<p class="description">${job.description}</p>
161+
162+
<!-- Requirements section -->
163+
${job.requirements && job.requirements.length > 0
164+
? html`
165+
<h4 class="section-heading">
166+
${t('job-offer-preview.requirements')}
167+
</h4>
168+
<ul class="requirements-list">
169+
${job.requirements.map(
170+
(req) => html`
171+
<li>${req}</li>
172+
`,
173+
)}
174+
</ul>
175+
`
176+
: ''}
177+
178+
<!-- Optional link -->
179+
${job.linkName && job.linkUrl
180+
? html`
181+
<p class="link-row">
182+
<a
183+
href="${job.linkUrl}"
184+
target="_blank"
185+
rel="noopener noreferrer">
186+
${job.linkName}
187+
</a>
188+
</p>
189+
`
190+
: ''}
191+
`
192+
: ''}
193+
</div>
194+
</dbp-modal>
195+
196+
<!-- Edit dialog — nested inside the preview dialog -->
197+
<dbp-bulletin-job-offer-dialog
198+
lang="${this.lang}"
199+
@dbp-job-offer-update="${(e) =>
200+
console.log('Updated job offer:', e.detail)}"></dbp-bulletin-job-offer-dialog>
201+
`;
202+
}
203+
204+
static get styles() {
205+
return css`
206+
${commonStyles.getThemeCSS()}
207+
${commonStyles.getGeneralCSS()}
208+
${commonStyles.getButtonCSS()}
209+
210+
.preview-title {
211+
margin: 0;
212+
font-size: 1.4rem;
213+
font-weight: 700;
214+
}
215+
216+
.preview-content {
217+
display: flex;
218+
flex-direction: column;
219+
gap: 1rem;
220+
}
221+
222+
/* Top meta row: left = key-value list, right = area badge */
223+
.meta-row {
224+
display: flex;
225+
justify-content: space-between;
226+
align-items: flex-start;
227+
gap: 1rem;
228+
}
229+
230+
.meta-fields {
231+
display: flex;
232+
flex-direction: column;
233+
gap: 0.1rem;
234+
}
235+
236+
.meta-item {
237+
margin: 0;
238+
font-size: 0.95rem;
239+
}
240+
241+
/* Area-of-interest badge */
242+
.area-badge {
243+
display: inline-block;
244+
padding: 0.25rem 0.75rem;
245+
border: 1px solid var(--dbp-content-surface, #333);
246+
border-radius: 2px;
247+
font-size: 0.9rem;
248+
white-space: nowrap;
249+
}
250+
251+
/* Action buttons row */
252+
.action-buttons {
253+
display: flex;
254+
gap: 0.75rem;
255+
flex-wrap: wrap;
256+
}
257+
258+
.action-btn {
259+
display: inline-flex;
260+
align-items: center;
261+
gap: 0.4rem;
262+
}
263+
264+
/* Icon inside buttons */
265+
.btn-icon {
266+
flex-shrink: 0;
267+
top: 0;
268+
}
269+
270+
/* Job description */
271+
.description {
272+
margin: 0;
273+
line-height: 1.6;
274+
}
275+
276+
/* Requirements heading */
277+
.section-heading {
278+
font-size: 1.1rem;
279+
font-weight: 700;
280+
margin: 0.5rem 0 0.5rem;
281+
}
282+
283+
/* Requirements list */
284+
.requirements-list {
285+
margin: 0;
286+
padding-left: 1.5rem;
287+
display: flex;
288+
flex-direction: column;
289+
gap: 0.4rem;
290+
}
291+
292+
.requirements-list li {
293+
line-height: 1.5;
294+
}
295+
296+
/* Optional link */
297+
.link-row {
298+
margin: 0;
299+
}
300+
`;
301+
}
302+
}
303+
304+
commonUtils.defineCustomElement('dbp-bulletin-job-offer-preview-dialog', JobOfferPreviewDialog);

src/dbp-bulletin-manage-job-offers.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Icon, IconButton} from '@dbp-toolkit/common';
66
import {TabulatorTable} from '@dbp-toolkit/tabulator-table/src/tabulator-table';
77
import DBPBulletinLitElement from './dbp-bulletin-lit-element.js';
88
import {JobOfferDialog} from './dbp-bulletin-job-offer-dialog.js';
9+
import {JobOfferPreviewDialog} from './dbp-bulletin-job-offer-preview-dialog.js';
910
import {MOCK_JOB_OFFERS} from './utils/mock.js';
1011

1112
class ManageJobOffers extends ScopedElementsMixin(DBPBulletinLitElement) {
@@ -15,6 +16,7 @@ class ManageJobOffers extends ScopedElementsMixin(DBPBulletinLitElement) {
1516
'dbp-icon-button': IconButton,
1617
'dbp-tabulator-table': TabulatorTable,
1718
'dbp-bulletin-job-offer-dialog': JobOfferDialog,
19+
'dbp-bulletin-job-offer-preview-dialog': JobOfferPreviewDialog,
1820
};
1921
}
2022

@@ -62,9 +64,12 @@ class ManageJobOffers extends ScopedElementsMixin(DBPBulletinLitElement) {
6264
}
6365
}
6466

65-
/** Placeholder handler for the preview/view action. */
67+
/** Opens the preview dialog for the given job offer. */
6668
onPreview(job) {
67-
console.log('Preview job offer:', job.identifier);
69+
const dialog = this._('dbp-bulletin-job-offer-preview-dialog');
70+
if (dialog) {
71+
dialog.open(job);
72+
}
6873
}
6974

7075
/** Opens the create job offer dialog (no pre-fill). */
@@ -210,6 +215,15 @@ class ManageJobOffers extends ScopedElementsMixin(DBPBulletinLitElement) {
210215
@dbp-job-offer-create="${(e) => console.log('New job offer:', e.detail)}"
211216
@dbp-job-offer-update="${(e) =>
212217
console.log('Updated job offer:', e.detail)}"></dbp-bulletin-job-offer-dialog>
218+
219+
<!-- Job offer preview dialog — opened by the keyword-research icon button -->
220+
<dbp-bulletin-job-offer-preview-dialog
221+
lang="${this.lang}"
222+
@dbp-job-offer-update="${(e) =>
223+
console.log(
224+
'Updated job offer:',
225+
e.detail,
226+
)}"></dbp-bulletin-job-offer-preview-dialog>
213227
`;
214228
}
215229

src/i18n/de/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616
"submit": "Bewerbung abschicken",
1717
"weekly-hours": "Anstellungsausmaß"
1818
},
19+
"job-offer-preview": {
20+
"deadline": "Bewerbungsfrist",
21+
"delete": "Löschen",
22+
"edit": "Bearbeiten",
23+
"organization": "Organisation",
24+
"published-at": "Veröffentlicht am",
25+
"requirements": "Anforderungen",
26+
"start-date": "Dienstbeginn",
27+
"view-applications": "Bewerbungen ansehen",
28+
"weekly-hours": "Anstellungsausmaß"
29+
},
1930
"manage-job-offers": {
2031
"col-id": "ID",
2132
"col-title": "Titel",

src/i18n/en/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616
"submit": "Submit application",
1717
"weekly-hours": "Weekly hours"
1818
},
19+
"job-offer-preview": {
20+
"deadline": "Application deadline",
21+
"delete": "Delete",
22+
"edit": "Edit",
23+
"organization": "Organisation",
24+
"published-at": "Published on",
25+
"requirements": "Requirements",
26+
"start-date": "Start date",
27+
"view-applications": "View applications",
28+
"weekly-hours": "Weekly hours"
29+
},
1930
"manage-job-offers": {
2031
"col-id": "ID",
2132
"col-title": "Title",

0 commit comments

Comments
 (0)