diff --git a/church_asset_tool/app.js b/church_asset_tool/app.js new file mode 100644 index 0000000..9bdfc4f --- /dev/null +++ b/church_asset_tool/app.js @@ -0,0 +1,118 @@ +const assetForm = document.getElementById('assetForm'); +const assetTableBody = document.querySelector('#assetTable tbody'); +const exportCsvBtn = document.getElementById('exportCsv'); +const exportXlsxBtn = document.getElementById('exportXlsx'); +const importFile = document.getElementById('importFile'); + +let assets = JSON.parse(localStorage.getItem('assets') || '[]'); +renderTable(); + +assetForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const type = document.getElementById('type').value; + const name = document.getElementById('name').value; + const description = document.getElementById('description').value; + const photoInput = document.getElementById('photo'); + + let photoData = ''; + if (photoInput.files[0]) { + photoData = await toBase64(photoInput.files[0]); + } + + const asset = { type, name, description, photoData }; + assets.push(asset); + localStorage.setItem('assets', JSON.stringify(assets)); + renderTable(); + assetForm.reset(); +}); + +exportCsvBtn.addEventListener('click', () => { + const csv = Papa.unparse(assets.map(a => ({ + type: a.type, + name: a.name, + description: a.description, + photoData: a.photoData + }))); + downloadFile(csv, 'assets.csv', 'text/csv;charset=utf-8;'); +}); + +exportXlsxBtn.addEventListener('click', () => { + const ws = XLSX.utils.json_to_sheet(assets.map(a => ({ + type: a.type, + name: a.name, + description: a.description, + photoData: a.photoData + }))); + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, 'Assets'); + const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); + downloadFile(new Blob([wbout], { type: 'application/octet-stream' }), 'assets.xlsx'); +}); + +importFile.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + const isCsv = file.name.endsWith('.csv'); + reader.onload = (evt) => { + if (isCsv) { + const parsed = Papa.parse(evt.target.result, { header: true }).data; + assets = parsed.filter(r => r.type).map(r => ({ + type: r.type, + name: r.name, + description: r.description, + photoData: r.photoData + })); + } else { + const wb = XLSX.read(evt.target.result, { type: 'binary' }); + const ws = wb.Sheets[wb.SheetNames[0]]; + const parsed = XLSX.utils.sheet_to_json(ws); + assets = parsed.map(r => ({ + type: r.type, + name: r.name, + description: r.description, + photoData: r.photoData + })); + } + localStorage.setItem('assets', JSON.stringify(assets)); + renderTable(); + }; + if (isCsv) { + reader.readAsText(file); + } else { + reader.readAsBinaryString(file); + } +}); + +function renderTable() { + assetTableBody.innerHTML = ''; + assets.forEach(asset => { + const tr = document.createElement('tr'); + tr.innerHTML = ` + ${asset.type} + ${asset.name} + ${asset.description} + ${asset.photoData ? `` : ''} + `; + assetTableBody.appendChild(tr); + }); +} + +function toBase64(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = error => reject(error); + reader.readAsDataURL(file); + }); +} + +function downloadFile(data, filename, type) { + const blob = data instanceof Blob ? data : new Blob([data], { type }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); +} diff --git a/church_asset_tool/index.html b/church_asset_tool/index.html new file mode 100644 index 0000000..8ac5e37 --- /dev/null +++ b/church_asset_tool/index.html @@ -0,0 +1,48 @@ + + + + + + 교회 자산 관리 + + + +
+

교회 자산 관리 도구

+
+ + + + + + + + + +
+
+ + + +
+ + + + + + + + + + +
유형이름설명사진
+
+ + + + + diff --git a/church_asset_tool/style.css b/church_asset_tool/style.css new file mode 100644 index 0000000..8fd973e --- /dev/null +++ b/church_asset_tool/style.css @@ -0,0 +1,37 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + display: flex; + justify-content: center; +} +.container { + max-width: 800px; + width: 100%; + padding: 20px; +} +form { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 20px; +} +table { + width: 100%; + border-collapse: collapse; +} +th, td { + border: 1px solid #ccc; + padding: 8px; + text-align: left; +} +.actions { + margin-bottom: 20px; + display: flex; + gap: 10px; + flex-wrap: wrap; +} +img.thumbnail { + max-width: 100px; + max-height: 100px; +}