domma-cms 0.7.6 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/plugins.json +0 -8
- package/package.json +1 -1
- package/plugins/job-board/admin/templates/application-detail.html +0 -40
- package/plugins/job-board/admin/templates/applications.html +0 -10
- package/plugins/job-board/admin/templates/companies.html +0 -24
- package/plugins/job-board/admin/templates/dashboard.html +0 -36
- package/plugins/job-board/admin/templates/job-editor.html +0 -17
- package/plugins/job-board/admin/templates/jobs.html +0 -15
- package/plugins/job-board/admin/templates/profile.html +0 -17
- package/plugins/job-board/admin/views/application-detail.js +0 -62
- package/plugins/job-board/admin/views/applications.js +0 -47
- package/plugins/job-board/admin/views/companies.js +0 -104
- package/plugins/job-board/admin/views/dashboard.js +0 -88
- package/plugins/job-board/admin/views/job-editor.js +0 -86
- package/plugins/job-board/admin/views/jobs.js +0 -53
- package/plugins/job-board/admin/views/profile.js +0 -47
- package/plugins/job-board/config.js +0 -6
- package/plugins/job-board/plugin.js +0 -466
- package/plugins/job-board/plugin.json +0 -40
- package/plugins/job-board/schemas/jb-agent-companies.json +0 -17
- package/plugins/job-board/schemas/jb-applications.json +0 -20
- package/plugins/job-board/schemas/jb-candidate-profiles.json +0 -20
- package/plugins/job-board/schemas/jb-companies.json +0 -21
- package/plugins/job-board/schemas/jb-jobs.json +0 -23
- package/plugins/theme-roller/admin/templates/theme-roller.html +0 -71
- package/plugins/theme-roller/admin/views/theme-roller-view.js +0 -403
- package/plugins/theme-roller/config.js +0 -1
- package/plugins/theme-roller/plugin.js +0 -233
- package/plugins/theme-roller/plugin.json +0 -31
- package/plugins/theme-roller/public/active-theme.css +0 -0
- package/plugins/theme-roller/public/inject-head-late.html +0 -1
package/config/plugins.json
CHANGED
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
"enabled": true,
|
|
4
4
|
"settings": {}
|
|
5
5
|
},
|
|
6
|
-
"theme-roller": {
|
|
7
|
-
"enabled": true,
|
|
8
|
-
"settings": {}
|
|
9
|
-
},
|
|
10
6
|
"site-search": {
|
|
11
7
|
"enabled": true,
|
|
12
8
|
"settings": {
|
|
@@ -17,10 +13,6 @@
|
|
|
17
13
|
"debounceMs": 300
|
|
18
14
|
}
|
|
19
15
|
},
|
|
20
|
-
"job-board": {
|
|
21
|
-
"enabled": false,
|
|
22
|
-
"settings": {}
|
|
23
|
-
},
|
|
24
16
|
"todo": {
|
|
25
17
|
"enabled": true,
|
|
26
18
|
"settings": {}
|
package/package.json
CHANGED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
<div class="view-header">
|
|
2
|
-
<div>
|
|
3
|
-
<h1 class="view-title">Application Detail</h1>
|
|
4
|
-
</div>
|
|
5
|
-
<div class="view-actions">
|
|
6
|
-
<a href="#/job-board/applications" class="btn btn-secondary">Back</a>
|
|
7
|
-
</div>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<div class="view-body">
|
|
11
|
-
<div class="card mb-4">
|
|
12
|
-
<div class="card-header"><h3>Application Info</h3></div>
|
|
13
|
-
<div class="card-body">
|
|
14
|
-
<dl class="dl-grid">
|
|
15
|
-
<dt>Job ID</dt> <dd id="app-job-id">—</dd>
|
|
16
|
-
<dt>Candidate</dt> <dd id="app-candidate">—</dd>
|
|
17
|
-
<dt>Status</dt> <dd id="app-status">—</dd>
|
|
18
|
-
<dt>Applied</dt> <dd id="app-applied">—</dd>
|
|
19
|
-
<dt>CV</dt> <dd id="app-cv">—</dd>
|
|
20
|
-
</dl>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<div class="card mb-4">
|
|
25
|
-
<div class="card-header"><h3>Cover Letter</h3></div>
|
|
26
|
-
<div class="card-body">
|
|
27
|
-
<p id="app-cover-letter" class="whitespace-pre-wrap">—</p>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<div class="card" id="status-section" style="display:none">
|
|
32
|
-
<div class="card-header"><h3>Update Status</h3></div>
|
|
33
|
-
<div class="card-body">
|
|
34
|
-
<div class="form-group">
|
|
35
|
-
<select id="status-select" class="form-control"></select>
|
|
36
|
-
</div>
|
|
37
|
-
<button class="btn btn-primary mt-2" id="btn-update-status">Update Status</button>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
<div class="view-header">
|
|
2
|
-
<div>
|
|
3
|
-
<h1 class="view-title">Companies</h1>
|
|
4
|
-
<p class="view-subtitle">Manage companies</p>
|
|
5
|
-
</div>
|
|
6
|
-
<div class="view-actions">
|
|
7
|
-
<button class="btn btn-primary" id="btn-create-company">Add Company</button>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<div class="view-body" id="companies-body">
|
|
12
|
-
<div id="agent-link-section" style="display:none" class="card mb-4">
|
|
13
|
-
<div class="card-header"><h3>Link to a Company</h3></div>
|
|
14
|
-
<div class="card-body">
|
|
15
|
-
<div class="form-group">
|
|
16
|
-
<label>Company ID</label>
|
|
17
|
-
<input type="text" id="link-company-id" class="form-control" placeholder="Enter company ID">
|
|
18
|
-
</div>
|
|
19
|
-
<button class="btn btn-secondary mt-2" id="btn-link-company">Link Company</button>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<div id="companies-table"></div>
|
|
24
|
-
</div>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<div class="view-header">
|
|
2
|
-
<div>
|
|
3
|
-
<h1 id="jb-heading" class="view-title">Job Board</h1>
|
|
4
|
-
<p class="view-subtitle">Overview and quick access</p>
|
|
5
|
-
</div>
|
|
6
|
-
</div>
|
|
7
|
-
|
|
8
|
-
<div class="view-body">
|
|
9
|
-
<div class="grid grid-cols-3 gap-4 mb-6">
|
|
10
|
-
<div class="card">
|
|
11
|
-
<div class="card-body text-center">
|
|
12
|
-
<div class="text-3xl font-bold" id="stat-jobs">—</div>
|
|
13
|
-
<div class="text-muted mt-1">Jobs</div>
|
|
14
|
-
</div>
|
|
15
|
-
</div>
|
|
16
|
-
<div class="card">
|
|
17
|
-
<div class="card-body text-center">
|
|
18
|
-
<div class="text-3xl font-bold" id="stat-apps">—</div>
|
|
19
|
-
<div class="text-muted mt-1">Applications</div>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
<div class="card" id="stat-companies-card">
|
|
23
|
-
<div class="card-body text-center">
|
|
24
|
-
<div class="text-3xl font-bold" id="stat-companies">—</div>
|
|
25
|
-
<div class="text-muted mt-1">Companies</div>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<div class="mb-4" id="quick-links"></div>
|
|
31
|
-
|
|
32
|
-
<div id="recent-apps-section">
|
|
33
|
-
<h2 class="section-title">Recent Applications</h2>
|
|
34
|
-
<div id="recent-apps-table"></div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<div class="view-header">
|
|
2
|
-
<div>
|
|
3
|
-
<h1 class="view-title" id="view-title">New Job</h1>
|
|
4
|
-
</div>
|
|
5
|
-
<div class="view-actions">
|
|
6
|
-
<a href="#/job-board/jobs" class="btn btn-secondary">Cancel</a>
|
|
7
|
-
<button class="btn btn-primary" id="btn-save">Save Job</button>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<div class="view-body">
|
|
12
|
-
<div class="card">
|
|
13
|
-
<div class="card-body">
|
|
14
|
-
<div id="job-form"></div>
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<div class="view-header">
|
|
2
|
-
<div>
|
|
3
|
-
<h1 class="view-title">Jobs</h1>
|
|
4
|
-
<p class="view-subtitle">Manage job listings</p>
|
|
5
|
-
</div>
|
|
6
|
-
<div class="view-actions">
|
|
7
|
-
<a href="#/job-board/jobs/new" class="btn btn-primary" id="btn-post-job">
|
|
8
|
-
<span data-icon="plus"></span> Post Job
|
|
9
|
-
</a>
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
<div class="view-body">
|
|
14
|
-
<div id="jobs-table"></div>
|
|
15
|
-
</div>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<div class="view-header">
|
|
2
|
-
<div>
|
|
3
|
-
<h1 class="view-title">My Profile</h1>
|
|
4
|
-
<p class="view-subtitle">Manage your candidate profile</p>
|
|
5
|
-
</div>
|
|
6
|
-
<div class="view-actions">
|
|
7
|
-
<button class="btn btn-primary" id="btn-save-profile">Save Profile</button>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<div class="view-body" id="profile-body">
|
|
12
|
-
<div class="card">
|
|
13
|
-
<div class="card-body">
|
|
14
|
-
<div id="profile-form"></div>
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { getRoleContext } from '../lib/role-context.js';
|
|
2
|
-
import { jbApi } from '../lib/api.js';
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
templateUrl: '/plugins/job-board/admin/templates/application-detail.html',
|
|
6
|
-
async onMount($container) {
|
|
7
|
-
const ctx = getRoleContext();
|
|
8
|
-
|
|
9
|
-
const hash = window.location.hash;
|
|
10
|
-
const match = hash.match(/\/job-board\/applications\/([^/]+)/);
|
|
11
|
-
const appId = match ? match[1] : null;
|
|
12
|
-
if (!appId) {
|
|
13
|
-
E.toast('Application ID not found', { type: 'error' });
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let application;
|
|
18
|
-
try {
|
|
19
|
-
application = await jbApi.applications.get(appId);
|
|
20
|
-
} catch {
|
|
21
|
-
E.toast('Failed to load application', { type: 'error' });
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const d = application.data || {};
|
|
26
|
-
$container.find('#app-job-id').text(d.job_id || '—');
|
|
27
|
-
$container.find('#app-candidate').text(d.candidate_id || '—');
|
|
28
|
-
$container.find('#app-status').text(d.status || '—');
|
|
29
|
-
$container.find('#app-applied').text(d.applied_at ? new Date(d.applied_at).toLocaleString() : '—');
|
|
30
|
-
$container.find('#app-cover-letter').text(d.cover_letter || '—');
|
|
31
|
-
if (d.cv_url) {
|
|
32
|
-
$container.find('#app-cv').html(`<a href="${d.cv_url}" target="_blank">View CV</a>`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Status update section — company/agent/admin only
|
|
36
|
-
if (!ctx.isCandidate) {
|
|
37
|
-
const statuses = ['submitted', 'reviewed', 'shortlisted', 'interview', 'offered', 'rejected', 'withdrawn'];
|
|
38
|
-
const $sel = $container.find('#status-select');
|
|
39
|
-
statuses.forEach(s => {
|
|
40
|
-
const opt = document.createElement('option');
|
|
41
|
-
opt.value = s;
|
|
42
|
-
opt.textContent = s;
|
|
43
|
-
if (s === d.status) opt.selected = true;
|
|
44
|
-
$sel.get(0)?.appendChild(opt);
|
|
45
|
-
});
|
|
46
|
-
$container.find('#status-section').show();
|
|
47
|
-
|
|
48
|
-
$container.find('#btn-update-status').get(0)?.addEventListener('click', async () => {
|
|
49
|
-
const status = $sel.val();
|
|
50
|
-
try {
|
|
51
|
-
await jbApi.applications.setStatus(appId, status);
|
|
52
|
-
E.toast('Status updated', { type: 'success' });
|
|
53
|
-
$container.find('#app-status').text(status);
|
|
54
|
-
} catch (err) {
|
|
55
|
-
E.toast(err.message || 'Failed to update status', { type: 'error' });
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
I.scan($container.get(0));
|
|
61
|
-
}
|
|
62
|
-
};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { getRoleContext } from '../lib/role-context.js';
|
|
2
|
-
import { jbApi } from '../lib/api.js';
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
templateUrl: '/plugins/job-board/admin/templates/applications.html',
|
|
6
|
-
async onMount($container) {
|
|
7
|
-
const ctx = getRoleContext();
|
|
8
|
-
|
|
9
|
-
let applications = [];
|
|
10
|
-
try {
|
|
11
|
-
const res = await jbApi.applications.list();
|
|
12
|
-
applications = Array.isArray(res) ? res : (res?.entries || []);
|
|
13
|
-
} catch {
|
|
14
|
-
E.toast('Failed to load applications', { type: 'error' });
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const statusBadge = (s) => {
|
|
19
|
-
const map = {
|
|
20
|
-
submitted: 'badge-info',
|
|
21
|
-
reviewed: 'badge-secondary',
|
|
22
|
-
shortlisted: 'badge-warning',
|
|
23
|
-
interview: 'badge-primary',
|
|
24
|
-
offered: 'badge-success',
|
|
25
|
-
rejected: 'badge-danger',
|
|
26
|
-
withdrawn: 'badge-secondary'
|
|
27
|
-
};
|
|
28
|
-
return `<span class="badge ${map[s] || 'badge-secondary'}">${s || '—'}</span>`;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const columns = [
|
|
32
|
-
{ key: 'data.company_id', label: 'Company' },
|
|
33
|
-
{ key: 'data.job_id', label: 'Job ID' },
|
|
34
|
-
{ key: 'data.candidate_id', label: 'Candidate' },
|
|
35
|
-
{ key: 'data.status', label: 'Status', render: v => statusBadge(v) },
|
|
36
|
-
{ key: 'data.applied_at', label: 'Applied', render: v => v ? new Date(v).toLocaleDateString() : '—' },
|
|
37
|
-
{
|
|
38
|
-
label: 'Actions',
|
|
39
|
-
render: (_, row) => `<a href="#/job-board/applications/${row.id}" class="btn btn-sm btn-secondary">View</a>`
|
|
40
|
-
}
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
T.create($container.find('#applications-table').get(0), { data: applications, columns });
|
|
44
|
-
|
|
45
|
-
I.scan($container.get(0));
|
|
46
|
-
}
|
|
47
|
-
};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { getRoleContext } from '../lib/role-context.js';
|
|
2
|
-
import { jbApi } from '../lib/api.js';
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
templateUrl: '/plugins/job-board/admin/templates/companies.html',
|
|
6
|
-
async onMount($container) {
|
|
7
|
-
const ctx = getRoleContext();
|
|
8
|
-
|
|
9
|
-
// Candidates don't see companies
|
|
10
|
-
if (ctx.isCandidate) {
|
|
11
|
-
$container.find('#companies-body').html('<p class="text-muted">Companies are not available in your view.</p>');
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Hide create button for agents (they link, not create)
|
|
16
|
-
if (ctx.isAgent) {
|
|
17
|
-
$container.find('#btn-create-company').hide();
|
|
18
|
-
$container.find('#agent-link-section').show();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const loadCompanies = async () => {
|
|
22
|
-
let companies = [];
|
|
23
|
-
try {
|
|
24
|
-
const res = await jbApi.companies.list();
|
|
25
|
-
companies = Array.isArray(res) ? res : (res?.entries || []);
|
|
26
|
-
} catch {
|
|
27
|
-
E.toast('Failed to load companies', { type: 'error' });
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
T.create($container.find('#companies-table').get(0), {
|
|
32
|
-
data: companies,
|
|
33
|
-
columns: [
|
|
34
|
-
{ key: 'data.name', label: 'Company Name' },
|
|
35
|
-
{ key: 'data.industry', label: 'Industry' },
|
|
36
|
-
{ key: 'data.location', label: 'Location' },
|
|
37
|
-
{ key: 'data.status', label: 'Status' }
|
|
38
|
-
]
|
|
39
|
-
});
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
await loadCompanies();
|
|
43
|
-
|
|
44
|
-
// Create company (jb-company role only)
|
|
45
|
-
if (ctx.isCompany || ctx.isAdmin) {
|
|
46
|
-
const modal = E.modal({ title: 'Create Company', size: 'md' });
|
|
47
|
-
|
|
48
|
-
const formContainer = document.createElement('div');
|
|
49
|
-
const form = F.create(formContainer, {
|
|
50
|
-
blueprint: [
|
|
51
|
-
{ name: 'name', label: 'Company Name', type: 'text', required: true },
|
|
52
|
-
{ name: 'industry', label: 'Industry', type: 'text' },
|
|
53
|
-
{ name: 'location', label: 'Location', type: 'text' },
|
|
54
|
-
{ name: 'size', label: 'Size', type: 'text', placeholder: 'e.g. 1-10, 50-100' }
|
|
55
|
-
]
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const saveBtn = document.createElement('button');
|
|
59
|
-
saveBtn.className = 'btn btn-primary mt-3';
|
|
60
|
-
saveBtn.textContent = 'Create Company';
|
|
61
|
-
formContainer.appendChild(saveBtn);
|
|
62
|
-
|
|
63
|
-
modal.element.appendChild(formContainer);
|
|
64
|
-
|
|
65
|
-
saveBtn.addEventListener('click', async () => {
|
|
66
|
-
const data = form.get();
|
|
67
|
-
if (!data.name) {
|
|
68
|
-
E.toast('Company name is required', { type: 'error' });
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
await jbApi.companies.create(data);
|
|
73
|
-
E.toast('Company created', { type: 'success' });
|
|
74
|
-
modal.close();
|
|
75
|
-
await loadCompanies();
|
|
76
|
-
} catch (err) {
|
|
77
|
-
E.toast(err.message || 'Failed to create company', { type: 'error' });
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
$container.find('#btn-create-company').get(0)?.addEventListener('click', () => modal.open());
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Agent link to company
|
|
85
|
-
if (ctx.isAgent) {
|
|
86
|
-
$container.find('#btn-link-company').get(0)?.addEventListener('click', async () => {
|
|
87
|
-
const companyId = $container.find('#link-company-id').val()?.trim();
|
|
88
|
-
if (!companyId) {
|
|
89
|
-
E.toast('Company ID is required', { type: 'error' });
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
try {
|
|
93
|
-
await jbApi.agentLinks.link({ company_id: companyId, role: 'agent' });
|
|
94
|
-
E.toast('Linked to company', { type: 'success' });
|
|
95
|
-
await loadCompanies();
|
|
96
|
-
} catch (err) {
|
|
97
|
-
E.toast(err.message || 'Failed to link company', { type: 'error' });
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
I.scan($container.get(0));
|
|
103
|
-
}
|
|
104
|
-
};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { getRoleContext } from '../lib/role-context.js';
|
|
2
|
-
import { jbApi } from '../lib/api.js';
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
templateUrl: '/plugins/job-board/admin/templates/dashboard.html',
|
|
6
|
-
async onMount($container) {
|
|
7
|
-
const ctx = getRoleContext();
|
|
8
|
-
|
|
9
|
-
// Load data
|
|
10
|
-
let jobs = [], applications = [], companies = [];
|
|
11
|
-
try {
|
|
12
|
-
[jobs, applications] = await Promise.all([
|
|
13
|
-
jbApi.jobs.list().catch(() => []),
|
|
14
|
-
jbApi.applications.list().catch(() => [])
|
|
15
|
-
]);
|
|
16
|
-
if (ctx.isAgent || ctx.isAdmin) {
|
|
17
|
-
companies = await jbApi.companies.list().catch(() => []);
|
|
18
|
-
}
|
|
19
|
-
} catch {}
|
|
20
|
-
|
|
21
|
-
// Ensure arrays
|
|
22
|
-
jobs = Array.isArray(jobs) ? jobs : (jobs?.entries || []);
|
|
23
|
-
applications = Array.isArray(applications) ? applications : (applications?.entries || []);
|
|
24
|
-
companies = Array.isArray(companies) ? companies : (companies?.entries || []);
|
|
25
|
-
|
|
26
|
-
// Stats
|
|
27
|
-
const activeJobs = jobs.filter(j => j.data?.status === 'published').length;
|
|
28
|
-
const totalJobs = jobs.length;
|
|
29
|
-
const totalApps = applications.length;
|
|
30
|
-
const newApps = applications.filter(a => a.data?.status === 'submitted').length;
|
|
31
|
-
|
|
32
|
-
// Render stats
|
|
33
|
-
$container.find('#stat-jobs').text(ctx.isCandidate ? totalApps : totalJobs);
|
|
34
|
-
$container.find('#stat-apps').text(ctx.isCandidate ? newApps : totalApps);
|
|
35
|
-
$container.find('#stat-companies').text(companies.length);
|
|
36
|
-
|
|
37
|
-
// Show/hide company stat based on role
|
|
38
|
-
if (!ctx.isAgent && !ctx.isAdmin) {
|
|
39
|
-
$container.find('#stat-companies-card').hide();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Role-specific heading
|
|
43
|
-
const headings = {
|
|
44
|
-
'jb-company': 'Company Dashboard',
|
|
45
|
-
'jb-agent': 'Agent Dashboard',
|
|
46
|
-
'jb-candidate':'My Job Applications',
|
|
47
|
-
'admin': 'Job Board Overview',
|
|
48
|
-
'manager': 'Job Board Overview'
|
|
49
|
-
};
|
|
50
|
-
$container.find('#jb-heading').text(headings[ctx.role] || 'Job Board');
|
|
51
|
-
|
|
52
|
-
// Role-specific quick links
|
|
53
|
-
const $links = $container.find('#quick-links');
|
|
54
|
-
if (ctx.isCompany || ctx.isAgent) {
|
|
55
|
-
$links.html(`
|
|
56
|
-
<a href="#/job-board/jobs/new" class="btn btn-primary">Post a Job</a>
|
|
57
|
-
<a href="#/job-board/applications" class="btn btn-secondary ml-2">View Applications</a>
|
|
58
|
-
`);
|
|
59
|
-
} else if (ctx.isCandidate) {
|
|
60
|
-
$links.html(`
|
|
61
|
-
<a href="#/job-board/jobs" class="btn btn-primary">Browse Jobs</a>
|
|
62
|
-
<a href="#/job-board/profile" class="btn btn-secondary ml-2">My Profile</a>
|
|
63
|
-
`);
|
|
64
|
-
} else {
|
|
65
|
-
$links.html(`
|
|
66
|
-
<a href="#/job-board/jobs" class="btn btn-primary">All Jobs</a>
|
|
67
|
-
<a href="#/job-board/companies" class="btn btn-secondary ml-2">Companies</a>
|
|
68
|
-
`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Recent applications table
|
|
72
|
-
const recentApps = applications.slice(0, 5);
|
|
73
|
-
if (recentApps.length) {
|
|
74
|
-
T.create($container.find('#recent-apps-table').get(0), {
|
|
75
|
-
data: recentApps,
|
|
76
|
-
columns: [
|
|
77
|
-
{ key: 'data.job_id', label: 'Job ID' },
|
|
78
|
-
{ key: 'data.status', label: 'Status' },
|
|
79
|
-
{ key: 'data.applied_at', label: 'Applied', render: v => v ? new Date(v).toLocaleDateString() : '—' }
|
|
80
|
-
]
|
|
81
|
-
});
|
|
82
|
-
} else {
|
|
83
|
-
$container.find('#recent-apps-section').hide();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
I.scan($container.get(0));
|
|
87
|
-
}
|
|
88
|
-
};
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { getRoleContext } from '../lib/role-context.js';
|
|
2
|
-
import { jbApi } from '../lib/api.js';
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
templateUrl: '/plugins/job-board/admin/templates/job-editor.html',
|
|
6
|
-
async onMount($container) {
|
|
7
|
-
const ctx = getRoleContext();
|
|
8
|
-
|
|
9
|
-
// Get job ID from hash if editing
|
|
10
|
-
const hash = window.location.hash;
|
|
11
|
-
const editMatch = hash.match(/\/job-board\/jobs\/edit\/([^/]+)/);
|
|
12
|
-
const jobId = editMatch ? editMatch[1] : null;
|
|
13
|
-
|
|
14
|
-
let job = null;
|
|
15
|
-
if (jobId) {
|
|
16
|
-
try {
|
|
17
|
-
job = await jbApi.jobs.get(jobId);
|
|
18
|
-
$container.find('#view-title').text('Edit Job');
|
|
19
|
-
} catch {
|
|
20
|
-
E.toast('Failed to load job', { type: 'error' });
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Load companies for agent/admin selector
|
|
26
|
-
let companies = [];
|
|
27
|
-
if (ctx.isAgent || ctx.isAdmin) {
|
|
28
|
-
try {
|
|
29
|
-
const res = await jbApi.companies.list();
|
|
30
|
-
companies = Array.isArray(res) ? res : (res?.entries || []);
|
|
31
|
-
} catch {}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Build form blueprint
|
|
35
|
-
const blueprint = [
|
|
36
|
-
{ name: 'title', label: 'Job Title', type: 'text', required: true },
|
|
37
|
-
{ name: 'description', label: 'Description', type: 'textarea' },
|
|
38
|
-
{ name: 'type', label: 'Job Type', type: 'select', options: ['full-time', 'part-time', 'contract', 'freelance', 'internship'] },
|
|
39
|
-
{ name: 'salary_min', label: 'Min Salary', type: 'number' },
|
|
40
|
-
{ name: 'salary_max', label: 'Max Salary', type: 'number' },
|
|
41
|
-
{ name: 'remote', label: 'Remote', type: 'checkbox' },
|
|
42
|
-
{ name: 'status', label: 'Status', type: 'select', options: ['draft', 'published', 'closed'] },
|
|
43
|
-
{ name: 'closes_at', label: 'Closes At', type: 'text', placeholder: 'YYYY-MM-DD' }
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
// Inject company selector for agents/admins
|
|
47
|
-
if ((ctx.isAgent || ctx.isAdmin) && companies.length) {
|
|
48
|
-
blueprint.splice(1, 0, {
|
|
49
|
-
name: 'company_id',
|
|
50
|
-
label: 'Company',
|
|
51
|
-
type: 'select',
|
|
52
|
-
options: companies.map(c => ({ value: c.id, label: c.data?.name || c.id }))
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const form = F.create($container.find('#job-form').get(0), { blueprint });
|
|
57
|
-
|
|
58
|
-
// Populate form if editing
|
|
59
|
-
if (job?.data) {
|
|
60
|
-
form.set(job.data);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Save handler
|
|
64
|
-
$container.find('#btn-save').get(0)?.addEventListener('click', async () => {
|
|
65
|
-
const data = form.get();
|
|
66
|
-
if (!data.title) {
|
|
67
|
-
E.toast('Job title is required', { type: 'error' });
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
if (jobId) {
|
|
73
|
-
await jbApi.jobs.update(jobId, data);
|
|
74
|
-
} else {
|
|
75
|
-
await jbApi.jobs.create(data);
|
|
76
|
-
}
|
|
77
|
-
E.toast('Job saved', { type: 'success' });
|
|
78
|
-
R.navigate('/job-board/jobs');
|
|
79
|
-
} catch (err) {
|
|
80
|
-
E.toast(err.message || 'Failed to save job', { type: 'error' });
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
I.scan($container.get(0));
|
|
85
|
-
}
|
|
86
|
-
};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { getRoleContext } from '../lib/role-context.js';
|
|
2
|
-
import { jbApi } from '../lib/api.js';
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
templateUrl: '/plugins/job-board/admin/templates/jobs.html',
|
|
6
|
-
async onMount($container) {
|
|
7
|
-
const ctx = getRoleContext();
|
|
8
|
-
|
|
9
|
-
// Candidates cannot post jobs
|
|
10
|
-
if (ctx.isCandidate) {
|
|
11
|
-
$container.find('#btn-post-job').hide();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let jobs = [];
|
|
15
|
-
try {
|
|
16
|
-
const res = await jbApi.jobs.list();
|
|
17
|
-
jobs = Array.isArray(res) ? res : (res?.entries || []);
|
|
18
|
-
} catch {
|
|
19
|
-
E.toast('Failed to load jobs', { type: 'error' });
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const statusBadge = (status) => {
|
|
24
|
-
const map = {
|
|
25
|
-
published: 'badge-success',
|
|
26
|
-
draft: 'badge-secondary',
|
|
27
|
-
closed: 'badge-danger'
|
|
28
|
-
};
|
|
29
|
-
return `<span class="badge ${map[status] || 'badge-secondary'}">${status || '—'}</span>`;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
T.create($container.find('#jobs-table').get(0), {
|
|
33
|
-
data: jobs,
|
|
34
|
-
columns: [
|
|
35
|
-
{ key: 'data.title', label: 'Title' },
|
|
36
|
-
{ key: 'data.status', label: 'Status', render: v => statusBadge(v) },
|
|
37
|
-
{ key: 'data.type', label: 'Type' },
|
|
38
|
-
{ key: 'data.remote', label: 'Remote', render: v => v ? 'Yes' : 'No' },
|
|
39
|
-
{
|
|
40
|
-
label: 'Actions',
|
|
41
|
-
render: (_, row) => {
|
|
42
|
-
if (ctx.isCandidate) {
|
|
43
|
-
return `<a href="#/job-board/jobs/${row.id}" class="btn btn-sm btn-secondary">View</a>`;
|
|
44
|
-
}
|
|
45
|
-
return `<a href="#/job-board/jobs/edit/${row.id}" class="btn btn-sm btn-primary">Edit</a>`;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
]
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
I.scan($container.get(0));
|
|
52
|
-
}
|
|
53
|
-
};
|