534 lines
20 KiB
JavaScript
534 lines
20 KiB
JavaScript
/* store.js — Lapisan data: localStorage + IndexedDB + Export/Import */
|
|
(function (global) {
|
|
'use strict';
|
|
|
|
var LS_KEY = 'erecruitment.v1';
|
|
var FILES_LS_KEY = 'erecruitment.files.v1';
|
|
var DB_NAME = 'erecruitment-files';
|
|
var DB_STORE = 'files';
|
|
var DB_VERSION = 1;
|
|
|
|
var DEFAULT_SETTINGS = { perusahaan: 'E-recruitment', hr: '', jabatanHr: '' };
|
|
|
|
var DEFAULT_STATUSES = [
|
|
{ id: 'baru', nama: 'Baru', warna: '#64748b', urutan: 1, isFinal: false },
|
|
{ id: 'screening', nama: 'Screening', warna: '#3b82f6', urutan: 2, isFinal: false },
|
|
{ id: 'interview', nama: 'Interview', warna: '#d97706', urutan: 3, isFinal: false },
|
|
{ id: 'diterima', nama: 'Diterima', warna: '#16a34a', urutan: 4, isFinal: true },
|
|
{ id: 'ditolak', nama: 'Ditolak', warna: '#dc2626', urutan: 5, isFinal: true },
|
|
{ id: 'gagal', nama: 'Gagal / Tidak Melanjutkan', warna: '#7c3aed', urutan: 6, isFinal: true }
|
|
];
|
|
|
|
var DEFAULT_TEMPLATES = [
|
|
{
|
|
nama: 'Konfirmasi Lamaran', jenis: 'umum', statusId: 'baru', isActive: true,
|
|
isi: 'Yth. {{nama}},\n\n' +
|
|
'Terima kasih atas ketertarikan Anda melamar posisi {{posisi}} di {{perusahaan}}. ' +
|
|
'Lamaran Anda telah kami terima dan akan kami proses sesuai tahapan yang berlaku. ' +
|
|
'Kami akan menghubungi Anda kembali apabila terdapat pembaruan.\n\n' +
|
|
'Hormat kami,\n{{hr}}\n{{perusahaan}}'
|
|
},
|
|
{
|
|
nama: 'Undangan Interview', jenis: 'umum', statusId: 'interview', isActive: true,
|
|
isi: 'Yth. {{nama}},\n\n' +
|
|
'Sehubungan dengan lamaran Anda untuk posisi {{posisi}} di {{perusahaan}}, kami mengundang Anda ' +
|
|
'untuk mengikuti tahap wawancara pada hari {{hari}}, {{tanggal}} pukul {{jam}} ({{metode}}).\n' +
|
|
'Lokasi/tautan: {{lokasi}}.\n' +
|
|
'Mohon konfirmasi ketersediaan Anda melalui pesan ini.\n\n' +
|
|
'Hormat kami,\n{{hr}}\n{{perusahaan}}'
|
|
},
|
|
{
|
|
nama: 'Reminder Interview', jenis: 'reminder', statusId: 'interview', isActive: true,
|
|
isi: 'Yth. {{nama}},\n\n' +
|
|
'Kami mengingatkan kembali jadwal wawancara Anda untuk posisi {{posisi}} di {{perusahaan}} ' +
|
|
'pada hari {{hari}}, {{tanggal}} pukul {{jam}} ({{metode}}).\n' +
|
|
'Lokasi/tautan: {{lokasi}}.\n' +
|
|
'Mohon hadir tepat waktu dan konfirmasi kehadiran Anda melalui pesan ini.\n\n' +
|
|
'Hormat kami,\n{{hr}}\n{{perusahaan}}'
|
|
},
|
|
{
|
|
nama: 'Reminder Interview (Pewawancara)', jenis: 'pewawancara', statusId: 'interview', isActive: true,
|
|
isi: 'Yth. {{pewawancara}},\n\n' +
|
|
'Kami mengingatkan jadwal wawancara untuk kandidat berikut:\n' +
|
|
'Nama: {{nama}}\n' +
|
|
'Posisi: {{posisi}}\n' +
|
|
'Waktu: {{hari}}, {{tanggal}} pukul {{jam}} ({{metode}})\n' +
|
|
'Lokasi/tautan: {{lokasi}}\n\n' +
|
|
'Mohon konfirmasi kesediaan Anda. Terima kasih.\n\n' +
|
|
'Hormat kami,\n{{hr}}\n{{perusahaan}}'
|
|
},
|
|
{
|
|
nama: 'Diterima', jenis: 'umum', statusId: 'diterima', isActive: true,
|
|
isi: 'Yth. {{nama}},\n\n' +
|
|
'Dengan senang hati kami sampaikan bahwa Anda diterima untuk posisi {{posisi}} di {{perusahaan}}. ' +
|
|
'Kami akan mengirimkan informasi lebih lanjut mengenai proses selanjutnya.\n' +
|
|
'Mohon konfirmasi kesediaan Anda melalui pesan ini.\n\n' +
|
|
'Hormat kami,\n{{hr}}\n{{perusahaan}}'
|
|
},
|
|
{
|
|
nama: 'Ditolak', jenis: 'umum', statusId: 'ditolak', isActive: true,
|
|
isi: 'Yth. {{nama}},\n\n' +
|
|
'Terima kasih atas waktu dan minat Anda melamar posisi {{posisi}} di {{perusahaan}}. ' +
|
|
'Setelah melalui proses evaluasi, dengan berat hati kami sampaikan bahwa lamaran Anda belum dapat ' +
|
|
'kami lanjutkan pada kesempatan ini. Kami menyimpan data Anda untuk peluang di masa mendatang.\n\n' +
|
|
'Hormat kami,\n{{hr}}\n{{perusahaan}}'
|
|
},
|
|
{
|
|
nama: 'Gagal / Tidak Melanjutkan', jenis: 'umum', statusId: 'gagal', isActive: true,
|
|
isi: 'Yth. {{nama}},\n\n' +
|
|
'Kami menindaklanjuti proses lamaran Anda untuk posisi {{posisi}} di {{perusahaan}}. ' +
|
|
'Berdasarkan catatan kami, proses seleksi Anda tidak dapat dilanjutkan. ' +
|
|
'Apabila terdapat kekeliruan, silakan menghubungi kami kembali.\n\n' +
|
|
'Hormat kami,\n{{hr}}\n{{perusahaan}}'
|
|
},
|
|
{
|
|
nama: 'Follow-up', jenis: 'umum', statusId: null, isActive: true,
|
|
isi: 'Yth. {{nama}},\n\n' +
|
|
'Kami ingin menindaklanjuti proses lamaran Anda untuk posisi {{posisi}} di {{perusahaan}}. ' +
|
|
'Mohon berkenan memberikan konfirmasi atas pesan ini.\n\n' +
|
|
'Hormat kami,\n{{hr}}\n{{perusahaan}}'
|
|
}
|
|
];
|
|
|
|
var state = null;
|
|
var storageOk = true;
|
|
|
|
/* ---------- Util ---------- */
|
|
function uid() {
|
|
if (global.crypto && global.crypto.randomUUID) {
|
|
try { return global.crypto.randomUUID(); } catch (e) { /* fallback */ }
|
|
}
|
|
return 'id-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
|
|
}
|
|
|
|
function clone(obj) { return JSON.parse(JSON.stringify(obj)); }
|
|
|
|
function seed() {
|
|
return {
|
|
version: 1,
|
|
settings: clone(DEFAULT_SETTINGS),
|
|
statuses: clone(DEFAULT_STATUSES),
|
|
templates: DEFAULT_TEMPLATES.map(function (t) {
|
|
return { id: uid(), nama: t.nama, jenis: t.jenis, statusId: t.statusId, isActive: t.isActive, isi: t.isi };
|
|
}),
|
|
candidates: []
|
|
};
|
|
}
|
|
|
|
/* ---------- IndexedDB ---------- */
|
|
function idbOpen() {
|
|
return new Promise(function (resolve, reject) {
|
|
if (!global.indexedDB) return reject(new Error('IndexedDB tidak tersedia'));
|
|
var req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
req.onupgradeneeded = function () {
|
|
var db = req.result;
|
|
if (!db.objectStoreNames.contains(DB_STORE)) db.createObjectStore(DB_STORE, { keyPath: 'id' });
|
|
};
|
|
req.onsuccess = function () { resolve(req.result); };
|
|
req.onerror = function () { reject(req.error); };
|
|
});
|
|
}
|
|
|
|
function idbPut(rec) {
|
|
return idbOpen().then(function (db) {
|
|
return new Promise(function (resolve, reject) {
|
|
var tx = db.transaction(DB_STORE, 'readwrite');
|
|
tx.objectStore(DB_STORE).put(rec);
|
|
tx.oncomplete = function () { resolve(rec); };
|
|
tx.onerror = function () { reject(tx.error); };
|
|
});
|
|
});
|
|
}
|
|
|
|
function idbGet(id) {
|
|
return idbOpen().then(function (db) {
|
|
return new Promise(function (resolve, reject) {
|
|
var tx = db.transaction(DB_STORE, 'readonly');
|
|
var req = tx.objectStore(DB_STORE).get(id);
|
|
req.onsuccess = function () { resolve(req.result || null); };
|
|
req.onerror = function () { reject(req.error); };
|
|
});
|
|
});
|
|
}
|
|
|
|
function idbDelete(id) {
|
|
return idbOpen().then(function (db) {
|
|
return new Promise(function (resolve, reject) {
|
|
var tx = db.transaction(DB_STORE, 'readwrite');
|
|
tx.objectStore(DB_STORE).delete(id);
|
|
tx.oncomplete = function () { resolve(true); };
|
|
tx.onerror = function () { reject(tx.error); };
|
|
});
|
|
});
|
|
}
|
|
|
|
function idbAll() {
|
|
return idbOpen().then(function (db) {
|
|
return new Promise(function (resolve, reject) {
|
|
var tx = db.transaction(DB_STORE, 'readonly');
|
|
var req = tx.objectStore(DB_STORE).getAll();
|
|
req.onsuccess = function () { resolve(req.result || []); };
|
|
req.onerror = function () { reject(req.error); };
|
|
});
|
|
});
|
|
}
|
|
|
|
function blobToDataURL(blob) {
|
|
return new Promise(function (resolve, reject) {
|
|
var fr = new FileReader();
|
|
fr.onload = function () { resolve(fr.result); };
|
|
fr.onerror = function () { reject(fr.error); };
|
|
fr.readAsDataURL(blob);
|
|
});
|
|
}
|
|
|
|
function dataURLToBlob(dataUrl) {
|
|
var parts = String(dataUrl).split(',');
|
|
var mime = (parts[0].match(/:(.*?);/) || [])[1] || 'application/octet-stream';
|
|
var bin = atob(parts[1]);
|
|
var arr = new Uint8Array(bin.length);
|
|
for (var i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
|
|
return new Blob([arr], { type: mime });
|
|
}
|
|
|
|
/* ---------- Fallback berkas via localStorage (bila IndexedDB diblokir) ---------- */
|
|
var FALLBACK_MAX = 2 * 1024 * 1024; // 2 MB per berkas
|
|
|
|
function lsFilesRead() {
|
|
try { return JSON.parse(global.localStorage.getItem(FILES_LS_KEY) || '{}') || {}; }
|
|
catch (e) { return {}; }
|
|
}
|
|
|
|
function lsFilesWrite(obj) {
|
|
global.localStorage.setItem(FILES_LS_KEY, JSON.stringify(obj));
|
|
}
|
|
|
|
function putFileRecord(rec) {
|
|
return idbPut(rec).catch(function () {
|
|
if (rec.size > FALLBACK_MAX) {
|
|
throw new Error('IndexedDB tidak tersedia dan berkas lebih dari 2 MB tidak dapat disimpan. Gunakan Link, atau buka aplikasi lewat server lokal.');
|
|
}
|
|
return blobToDataURL(rec.blob).then(function (dataUrl) {
|
|
var all = lsFilesRead();
|
|
all[rec.id] = { id: rec.id, name: rec.name, type: rec.type, size: rec.size, dataUrl: dataUrl };
|
|
try { lsFilesWrite(all); }
|
|
catch (e) { throw new Error('Penyimpanan browser penuh. Gunakan Link atau hapus berkas lain.'); }
|
|
return rec;
|
|
});
|
|
});
|
|
}
|
|
|
|
function getFileRecord(id) {
|
|
return idbGet(id).catch(function () { return null; }).then(function (rec) {
|
|
if (rec) return rec;
|
|
var all = lsFilesRead();
|
|
return all[id] || null;
|
|
});
|
|
}
|
|
|
|
function allFileRecords() {
|
|
return idbAll().catch(function () { return []; }).then(function (rows) {
|
|
var all = lsFilesRead();
|
|
var seen = {};
|
|
var out = [];
|
|
rows.forEach(function (r) { seen[r.id] = 1; out.push(r); });
|
|
Object.keys(all).forEach(function (k) { if (!seen[k]) out.push(all[k]); });
|
|
return out;
|
|
});
|
|
}
|
|
|
|
/* ---------- Persistence ---------- */
|
|
function load() {
|
|
try {
|
|
var raw = global.localStorage.getItem(LS_KEY);
|
|
if (!raw) return seed();
|
|
var parsed = JSON.parse(raw);
|
|
if (!parsed || typeof parsed !== 'object') return seed();
|
|
var base = seed();
|
|
return {
|
|
version: 1,
|
|
settings: Object.assign(base.settings, parsed.settings || {}),
|
|
statuses: Array.isArray(parsed.statuses) && parsed.statuses.length ? parsed.statuses : base.statuses,
|
|
templates: Array.isArray(parsed.templates) && parsed.templates.length ? parsed.templates : base.templates,
|
|
candidates: Array.isArray(parsed.candidates) ? parsed.candidates : []
|
|
};
|
|
} catch (e) {
|
|
console.error('Gagal memuat data:', e);
|
|
return seed();
|
|
}
|
|
}
|
|
|
|
function persist() {
|
|
try {
|
|
global.localStorage.setItem(LS_KEY, JSON.stringify(state));
|
|
} catch (e) {
|
|
// Penyimpanan tidak tersedia/penuh: jangan crash, pakai memori saja.
|
|
storageOk = false;
|
|
console.warn('Penyimpanan browser tidak tersedia / penuh. Data hanya bertahan selama tab terbuka.', e);
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
state = load();
|
|
if (state.candidates.some(function (c) { return !c.id; })) {
|
|
state.candidates.forEach(function (c) { if (!c.id) c.id = uid(); });
|
|
}
|
|
persist();
|
|
if (!storageOk && global.UI && global.UI.banner) {
|
|
global.UI.banner('Penyimpanan browser (localStorage) tidak dapat digunakan. Data hanya tersimpan sementara sampai tab ditutup — jangan tutup tab sebelum Export data. Disarankan membuka aplikasi lewat server lokal (lihat README).', 'err');
|
|
}
|
|
return state;
|
|
}
|
|
|
|
function storageAvailable() { return storageOk; }
|
|
|
|
function get() { return state; }
|
|
function save() { persist(); }
|
|
|
|
function statusById(id) {
|
|
return state.statuses.filter(function (s) { return s.id === id; })[0] || null;
|
|
}
|
|
function templateById(id) {
|
|
return state.templates.filter(function (t) { return t.id === id; })[0] || null;
|
|
}
|
|
function candidateById(id) {
|
|
return state.candidates.filter(function (c) { return c.id === id; })[0] || null;
|
|
}
|
|
function sortedStatuses() {
|
|
return state.statuses.slice().sort(function (a, b) { return (a.urutan || 0) - (b.urutan || 0); });
|
|
}
|
|
|
|
/* ---------- Candidates ---------- */
|
|
function normalizeCandidate(data) {
|
|
return {
|
|
nama: (data.nama || '').trim(),
|
|
phoneRaw: (data.phoneRaw || '').trim(),
|
|
phoneNorm: data.phoneNorm || '',
|
|
email: (data.email || '').trim(),
|
|
posisi: (data.posisi || '').trim(),
|
|
statusId: data.statusId || (sortedStatuses()[0] ? sortedStatuses()[0].id : 'baru'),
|
|
sumber: data.sumber || '',
|
|
domisili: (data.domisili || '').trim(),
|
|
gajiEkspektasi: (data.gajiEkspektasi || '').trim(),
|
|
tanggalMelamar: data.tanggalMelamar || new Date().toISOString().slice(0, 10),
|
|
catatan: (data.catatan || '').trim(),
|
|
interview: {
|
|
tanggal: (data.interview && data.interview.tanggal) || '',
|
|
jam: (data.interview && data.interview.jam) || '',
|
|
metode: (data.interview && data.interview.metode) || '',
|
|
lokasi: (data.interview && data.interview.lokasi) || '',
|
|
pewawancara: (data.interview && data.interview.pewawancara) || '',
|
|
telpPewawancara: (data.interview && data.interview.telpPewawancara) || ''
|
|
},
|
|
berkas: data.berkas || { cv: null, test: null, portofolio: null }
|
|
};
|
|
}
|
|
|
|
function addCandidate(data) {
|
|
var now = new Date().toISOString();
|
|
var c = normalizeCandidate(data);
|
|
c.id = uid();
|
|
c.history = [{ statusLama: null, statusBaru: c.statusId, at: now }];
|
|
c.createdAt = now;
|
|
c.updatedAt = now;
|
|
state.candidates.unshift(c);
|
|
persist();
|
|
return c;
|
|
}
|
|
|
|
function updateCandidate(id, data) {
|
|
var c = candidateById(id);
|
|
if (!c) return null;
|
|
var before = c.statusId;
|
|
var next = normalizeCandidate(Object.assign({ berkas: c.berkas }, data));
|
|
Object.keys(next).forEach(function (k) { c[k] = next[k]; });
|
|
c.updatedAt = new Date().toISOString();
|
|
if (before !== c.statusId) {
|
|
c.history = c.history || [];
|
|
c.history.push({ statusLama: before, statusBaru: c.statusId, at: c.updatedAt });
|
|
}
|
|
persist();
|
|
return c;
|
|
}
|
|
|
|
function deleteCandidate(id) {
|
|
var c = candidateById(id);
|
|
if (!c) return false;
|
|
['cv', 'test', 'portofolio'].forEach(function (slot) {
|
|
var b = c.berkas && c.berkas[slot];
|
|
if (b && b.mode === 'file' && b.fileId) deleteFile(b.fileId).catch(function () {});
|
|
});
|
|
state.candidates = state.candidates.filter(function (x) { return x.id !== id; });
|
|
persist();
|
|
return true;
|
|
}
|
|
|
|
/* ---------- Berkas ---------- */
|
|
function saveFile(file) {
|
|
var rec = {
|
|
id: uid(),
|
|
name: file.name,
|
|
type: file.type || 'application/octet-stream',
|
|
size: file.size,
|
|
blob: file
|
|
};
|
|
return putFileRecord(rec).then(function () {
|
|
return { id: rec.id, name: rec.name, type: rec.type, size: rec.size };
|
|
});
|
|
}
|
|
|
|
function getFileUrl(fileId) {
|
|
return getFileRecord(fileId).then(function (rec) {
|
|
if (!rec) throw new Error('Berkas tidak ditemukan');
|
|
if (rec.blob) return URL.createObjectURL(rec.blob);
|
|
if (rec.dataUrl) return rec.dataUrl;
|
|
throw new Error('Berkas tidak ditemukan');
|
|
});
|
|
}
|
|
|
|
function deleteFile(fileId) {
|
|
var all = lsFilesRead();
|
|
if (all[fileId]) {
|
|
delete all[fileId];
|
|
try { lsFilesWrite(all); } catch (e) { /* abaikan */ }
|
|
}
|
|
return idbDelete(fileId).catch(function () { return true; });
|
|
}
|
|
|
|
function fileCount() {
|
|
return allFileRecords().then(function (rows) { return rows.length; });
|
|
}
|
|
|
|
/* ---------- Templates ---------- */
|
|
function addTemplate(data) {
|
|
var t = {
|
|
id: uid(),
|
|
nama: (data.nama || 'Tanpa nama').trim(),
|
|
jenis: data.jenis || 'umum',
|
|
statusId: data.statusId || null,
|
|
isActive: data.isActive !== false,
|
|
isi: data.isi || ''
|
|
};
|
|
state.templates.push(t);
|
|
persist();
|
|
return t;
|
|
}
|
|
|
|
function updateTemplate(id, data) {
|
|
var t = templateById(id);
|
|
if (!t) return null;
|
|
if (data.nama != null) t.nama = (data.nama || '').trim();
|
|
if (data.jenis != null) t.jenis = data.jenis;
|
|
if (data.statusId !== undefined) t.statusId = data.statusId || null;
|
|
if (data.isActive != null) t.isActive = !!data.isActive;
|
|
if (data.isi != null) t.isi = data.isi;
|
|
persist();
|
|
return t;
|
|
}
|
|
|
|
function deleteTemplate(id) {
|
|
state.templates = state.templates.filter(function (t) { return t.id !== id; });
|
|
persist();
|
|
}
|
|
|
|
/* ---------- Settings / Statuses ---------- */
|
|
function setSettings(patch) {
|
|
Object.assign(state.settings, patch);
|
|
persist();
|
|
}
|
|
|
|
function saveStatus(status) {
|
|
if (status.id && statusById(status.id)) {
|
|
var s = statusById(status.id);
|
|
s.nama = status.nama;
|
|
s.warna = status.warna;
|
|
s.urutan = status.urutan != null ? status.urutan : s.urutan;
|
|
s.isFinal = !!status.isFinal;
|
|
} else {
|
|
var maxUrut = state.statuses.reduce(function (m, x) { return Math.max(m, x.urutan || 0); }, 0);
|
|
state.statuses.push({
|
|
id: status.id || uid(),
|
|
nama: status.nama,
|
|
warna: status.warna || '#64748b',
|
|
urutan: status.urutan != null ? status.urutan : maxUrut + 1,
|
|
isFinal: !!status.isFinal
|
|
});
|
|
}
|
|
persist();
|
|
}
|
|
|
|
function deleteStatus(id) {
|
|
if (state.candidates.some(function (c) { return c.statusId === id; })) {
|
|
throw new Error('Status masih dipakai oleh pelamar.');
|
|
}
|
|
state.statuses = state.statuses.filter(function (s) { return s.id !== id; });
|
|
state.templates.forEach(function (t) { if (t.statusId === id) t.statusId = null; });
|
|
persist();
|
|
}
|
|
|
|
/* ---------- Export / Import ---------- */
|
|
function exportData() {
|
|
return allFileRecords().then(function (rows) {
|
|
return Promise.all(rows.map(function (r) {
|
|
var asUrl = r.dataUrl ? Promise.resolve(r.dataUrl) : blobToDataURL(r.blob);
|
|
return asUrl.then(function (dataUrl) {
|
|
return { id: r.id, name: r.name, type: r.type, size: r.size, dataUrl: dataUrl };
|
|
});
|
|
})).then(function (files) {
|
|
return {
|
|
app: 'e-recruitment',
|
|
version: 1,
|
|
exportedAt: new Date().toISOString(),
|
|
settings: state.settings,
|
|
statuses: state.statuses,
|
|
templates: state.templates,
|
|
candidates: state.candidates,
|
|
files: files
|
|
};
|
|
});
|
|
});
|
|
}
|
|
|
|
function importData(payload) {
|
|
if (!payload || payload.app !== 'e-recruitment') {
|
|
return Promise.reject(new Error('File backup tidak dikenali.'));
|
|
}
|
|
var files = payload.files || [];
|
|
return Promise.all(files.map(function (f) {
|
|
return dataURLToBlob(f.dataUrl).then(function (blob) {
|
|
return putFileRecord({ id: f.id, name: f.name, type: f.type, size: f.size, blob: blob });
|
|
});
|
|
})).then(function () {
|
|
state = {
|
|
version: 1,
|
|
settings: Object.assign(clone(DEFAULT_SETTINGS), payload.settings || {}),
|
|
statuses: Array.isArray(payload.statuses) && payload.statuses.length ? payload.statuses : clone(DEFAULT_STATUSES),
|
|
templates: Array.isArray(payload.templates) ? payload.templates : [],
|
|
candidates: Array.isArray(payload.candidates) ? payload.candidates : []
|
|
};
|
|
persist();
|
|
return state;
|
|
});
|
|
}
|
|
|
|
function reset() {
|
|
state = seed();
|
|
persist();
|
|
try { lsFilesWrite({}); } catch (e) { /* abaikan */ }
|
|
return allFileRecords().then(function (rows) {
|
|
return Promise.all(rows.map(function (r) { return deleteFile(r.id); }));
|
|
}).then(function () { return state; });
|
|
}
|
|
|
|
global.Store = {
|
|
init: init, get: get, save: save, seed: seed, uid: uid, storageAvailable: storageAvailable,
|
|
statusById: statusById, templateById: templateById, candidateById: candidateById,
|
|
sortedStatuses: sortedStatuses,
|
|
addCandidate: addCandidate, updateCandidate: updateCandidate, deleteCandidate: deleteCandidate,
|
|
saveFile: saveFile, getFileUrl: getFileUrl, deleteFile: deleteFile, fileCount: fileCount,
|
|
addTemplate: addTemplate, updateTemplate: updateTemplate, deleteTemplate: deleteTemplate,
|
|
setSettings: setSettings, saveStatus: saveStatus, deleteStatus: deleteStatus,
|
|
exportData: exportData, importData: importData, reset: reset
|
|
};
|
|
})(window);
|