Download static/returns-admin.js from adoomy/StocKing: direct link, hf CLI and curl.
- Browser
- Download file 13.8 kB
-
https://huggingface.co/spaces/adoomy/StocKing/resolve/main/static/returns-admin.js
- Command line
-
hf download hf://spaces/adoomy/StocKing/static/returns-admin.js
-
curl -L -o returns-admin.js https://huggingface.co/spaces/adoomy/StocKing/resolve/main/static/returns-admin.js
13.8 kB
| let returnDocuments = []; | |
| let editingReturnDocument = null; | |
| const $return = id => document.getElementById(id); | |
| const returnReviewYear = Number(new Intl.DateTimeFormat('en-US', {timeZone:'Asia/Seoul',year:'numeric'}).format(new Date())); | |
| function returnDocumentYears(doc, byId) { | |
| const visited = new Set(); | |
| while (doc && !visited.has(doc.id)) { | |
| visited.add(doc.id); | |
| const years = [...new Set([...(doc.events || []), ...(doc.policies || [])] | |
| .map(item => item.year).filter(year => Number.isInteger(year) && year >= 2000 && year <= 2100))]; | |
| if (years.length) return years.sort((a,b) => a-b); | |
| // A withdrawal or unstructured correction can inherit its original's period. | |
| doc = byId.get(doc.supersedes_id); | |
| } | |
| return []; | |
| } | |
| function matchesReturnPeriod(years, period, currentYear = returnReviewYear) { | |
| if (period === 'all') return true; | |
| if (period === 'unknown') return years.length === 0; | |
| if (period === 'history') return years.some(year => year < currentYear); | |
| return years.some(year => year === currentYear || year === currentYear+1); | |
| } | |
| async function returnAdminRequest(path, options = {}) { | |
| const response = await fetch('/api/admin/returns/' + path, options); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| if (response.status === 401 || response.status === 403) throw new Error('κ΄λ¦¬μ λ‘κ·ΈμΈμ΄ νμν©λλ€. μ€ν¬λ¦¬λμμ λ‘κ·ΈμΈνμΈμ.'); | |
| const message = typeof data.detail === 'string' ? data.detail : Array.isArray(data.detail) | |
| ? data.detail.map(item => String(item.msg).replace(/^Value error, /,'')).join(' Β· ') : 'μμ²μ μ²λ¦¬νμ§ λͺ»νμ΅λλ€.'; | |
| throw new Error(message); | |
| } | |
| return data; | |
| } | |
| function adminMessage(message) { $return('admin-message').textContent = message; } | |
| function field(label, name, value, type = 'text', choices = null) { | |
| const wrap = document.createElement('label'); | |
| wrap.textContent = label; | |
| const input = document.createElement(choices ? 'select' : type === 'textarea' ? 'textarea' : 'input'); | |
| input.name = name; | |
| if (choices) { | |
| for (const [key, text] of choices) input.add(new Option(text, key)); | |
| input.value = String(value ?? choices[0][0]); | |
| } else if (type === 'checkbox') { input.type = type; input.checked = !!value; } | |
| else { if (type !== 'textarea') input.type = type; input.value = value ?? ''; } | |
| if (type === 'number') input.step = 'any'; | |
| wrap.append(input); | |
| return wrap; | |
| } | |
| const returnScopes = [['unknown','λ²μ λ―ΈνμΈ'],['common','보ν΅μ£Ό'],['company','νμ¬ μ 체'],['preferred','μ°μ μ£Ό']]; | |
| function addReturnItem(kind, value = {}) { | |
| const block = document.createElement('fieldset'); | |
| block.className = 'return-item return-fields'; | |
| block.append(field('μ μ© μ°λ','year', value.year ?? returnReviewYear, 'number')); | |
| block.append(field('μ£Όμ λ²μ','scope', value.scope, 'text', returnScopes)); | |
| if (kind === 'event') { | |
| block.append(field('μκ°μμ κΈμ‘(μ΅μ)','amount', value.amount,'number')); | |
| block.append(field('μκ°μμ μΌ','cancellation_date',value.cancellation_date,'date')); | |
| block.append(field('μ°κ° λͺ©ν μΈ μΆκ° μκ° νμΈ','outside_policy',value.outside_policy,'checkbox')); | |
| } else { | |
| block.append(field('μ μ± μ’ λ₯','kind',value.kind,'text', [['cancellation','μ°κ° μκ° κΈμ‘'],['total_amount','μ΄νμ κΈμ‘'],['total_ratio','μ΄νμμ¨'],['none','μκ° κ³ν μμ νμΈ']])); | |
| block.append(field('λͺ©ν κΈμ‘(μ΅μ)','amount',value.amount,'number')); | |
| block.append(field('λͺ©ν νμμ¨(0.5 = 50%)','ratio',value.ratio,'number')); | |
| block.append(field('νμ¬μ μκ° λ°©μΉ¨ νμΈ','cancellation_commitment',value.cancellation_commitment,'checkbox')); | |
| block.append(field('λͺ©ν μ΄μ΅ κΈ°μ€','income_basis',value.income_basis ?? 'unknown','text', [['unknown','μ§μ νμΈ νμ'],['net_income','μμ§λ λΉκΈ°μμ΄μ΅'],['owner_net_income','μ§λ°°μ£Όμ£Ό μμ΄μ΅'],['adjusted','μ‘°μ μ΄μ΅ Β· μ§μ 보μ']])); | |
| block.append(field('λμΌ κΈ°μ€ μμ μ΄μ΅ 보μ(μ΅μ)','income_amount',value.income_amount,'number')); | |
| block.append(field('λμΌ λ²μ μμ λ°°λΉμ΄μ‘ 보μ(μ΅μ)','dividend_amount',value.dividend_amount,'number')); | |
| } | |
| block.append(field('κΈμ‘Β·λ²μΒ·κ°μ μ κ·Όκ±°','evidence',value.evidence,'textarea')); | |
| const remove = document.createElement('button'); remove.type = 'button'; remove.textContent = 'νλͺ© μμ '; | |
| remove.addEventListener('click', () => block.remove()); block.append(remove); | |
| $return(kind + '-fields').append(block); | |
| } | |
| function editReturnDocument(doc = null) { | |
| editingReturnDocument = doc; | |
| $return('review-section').hidden = false; | |
| const form = $return('review-form'); form.reset(); | |
| for (const name of ['ticker','title','source_url','published_at']) form.elements[name].value = doc?.[name] ?? ''; | |
| form.elements.review_evidence.value = doc?.evidence ?? ''; | |
| form.elements.ticker.readOnly = !!doc; | |
| form.elements.is_withdrawal.checked = !!doc?.is_withdrawal; | |
| const parent = form.elements.supersedes_id; parent.replaceChildren(new Option('μμ','')); | |
| for (const other of returnDocuments) if (other.id !== doc?.id) parent.add(new Option(`${other.ticker} Β· #${other.id} ${other.title}`,other.id)); | |
| parent.value = doc?.supersedes_id ?? ''; | |
| $return('event-fields').replaceChildren(); $return('policy-fields').replaceChildren(); | |
| for (const item of doc?.events ?? []) addReturnItem('event', item); | |
| for (const item of doc?.policies ?? []) addReturnItem('policy', item); | |
| $return('audit-output').textContent = ''; | |
| $return('show-audit').disabled = !doc; | |
| $return('review-section').scrollIntoView({behavior:'smooth'}); | |
| } | |
| function renderReturnDocuments() { | |
| const state = $return('status-filter').value; | |
| const period = $return('period-filter').value || 'current'; | |
| const byId = new Map(returnDocuments.map(doc => [doc.id,doc])); | |
| const yearsById = new Map(returnDocuments.map(doc => [doc.id,returnDocumentYears(doc,byId)])); | |
| const tbody = $return('document-list'); tbody.replaceChildren(); | |
| const periodDocuments = returnDocuments.filter(doc => matchesReturnPeriod(yearsById.get(doc.id),period)); | |
| const shown = periodDocuments.filter(d => state === 'all' || (state === 'active' ? ['active','approved'].includes(d.status) : state === 'reference' ? ['reference','pending'].includes(d.status) : d.status === state)); | |
| const labels = {current:`${returnReviewYear}Β·${returnReviewYear+1}λ λμ`,history:`κ³Όκ±° μ΄λ ₯ (${returnReviewYear-1}λ μ΄μ )`,unknown:'μ μ© μ°λ λ―ΈνμΈ',all:'μ 체 μ°λ'}; | |
| $return('document-status').textContent = `νμ ${shown.length.toLocaleString('ko-KR')}κ° Β· ${labels[period]} ${periodDocuments.length.toLocaleString('ko-KR')}κ° Β· μ 체 ${returnDocuments.length.toLocaleString('ko-KR')}κ°`; | |
| if (!shown.length) { | |
| const row = document.createElement('tr'); const td = document.createElement('td'); td.colSpan = 6; | |
| td.textContent = returnDocuments.length ? 'μ νν 쑰건μ κ²ν μλ£κ° μμ΅λλ€. μλ£ κ΅¬λΆ λλ νμ μνλ₯Ό λ³κ²½νμΈμ.' : 'μμ§ μμ§λ κ²ν μλ£κ° μμ΅λλ€.'; | |
| row.append(td); tbody.append(row); | |
| } | |
| for (const doc of shown) { | |
| const row = document.createElement('tr'); | |
| for (const text of [doc.ticker,doc.title, yearsById.get(doc.id).join(' Β· ') || 'λ―ΈνμΈ', {active:'μλ λ°μ',reference:'κ·Όκ±°λ§ λ³΄κ΄',pending:'κ·Όκ±°λ§ λ³΄κ΄',approved:'λ°μ μ€',excluded:'λ°μ μ·¨μ'}[doc.status],doc.error || 'μμ§ μλ£']) { | |
| const td = document.createElement('td'); td.textContent = text; row.append(td); | |
| } | |
| const td = document.createElement('td'); const button = document.createElement('button'); button.textContent = 'κ·Όκ±°Β·μμ '; | |
| button.addEventListener('click', () => editReturnDocument(doc)); td.append(button); row.append(td); tbody.append(row); | |
| const toggle = document.createElement('button'); toggle.textContent = doc.status === 'excluded' ? 'λ€μ λ°μ' : 'λ°μ μ·¨μ'; | |
| toggle.addEventListener('click', async () => { | |
| toggle.disabled = true; | |
| try { | |
| await setReturnSourceStatus(doc.id, doc.status === 'excluded' ? 'restore' : 'cancel'); | |
| await loadReturnAdmin(); | |
| adminMessage(doc.status === 'excluded' ? 'λ€μ λ°μνμ΅λλ€.' : 'κ³μ°μμ μ μΈνμ΅λλ€. μλ³Έκ³Ό λ³κ²½ μ΄λ ₯μ 보쑴λλ©° λ€μ λ°μν μ μμ΅λλ€.'); | |
| } catch (error) { adminMessage(error.message); toggle.disabled = false; } | |
| }); td.append(toggle); | |
| } | |
| } | |
| async function loadReturnDocuments() { | |
| $return('document-status').textContent = 'κ²ν μλ£λ₯Ό λΆλ¬μ€λ μ€μ λλ€β¦'; | |
| try { | |
| returnDocuments = await returnAdminRequest('documents'); | |
| renderReturnDocuments(); | |
| } catch (error) { | |
| $return('document-status').textContent = 'κ²ν μλ£λ₯Ό λΆλ¬μ€μ§ λͺ»νμ΅λλ€. ' + error.message; | |
| throw error; | |
| } | |
| } | |
| async function loadReturnSources() { | |
| let sources; | |
| try { sources = await returnAdminRequest('sources'); } | |
| catch (error) { $return('source-list').textContent = 'IR μΆμ²λ₯Ό λΆλ¬μ€μ§ λͺ»νμ΅λλ€. ' + error.message; throw error; } | |
| $return('source-list').replaceChildren(); | |
| for (const source of sources) { | |
| const p = document.createElement('p'); | |
| p.textContent = `${source.ticker} Β· ${source.url} Β· ${source.enabled ? 'μμ§ μ¬μ©' : 'μμ§ μ€μ§'} Β· ${source.error || (source.last_success_at ? 'μ΅κ·Ό μμ§ ' + source.last_success_at : 'μμ§ μμ§λμ§ μμ')}`; | |
| const button = document.createElement('button'); button.textContent = 'μμ '; | |
| button.addEventListener('click', () => { | |
| const f = $return('source-form'); | |
| for (const name of ['ticker','url','is_index','enabled']) f.elements[name].value = String(source[name]); | |
| }); p.append(button); $return('source-list').append(p); | |
| } | |
| } | |
| async function loadReturnAdmin() { | |
| const results = await Promise.allSettled([loadReturnDocuments(), loadReturnSources()]); | |
| const errors = results.filter(r => r.status === 'rejected').map(r => r.reason.message); | |
| if (errors.length) throw new Error([...new Set(errors)].join(' Β· ')); | |
| } | |
| function readReturnItems(kind) { | |
| return [...$return(kind + '-fields').children].map(block => { | |
| const item = {}; | |
| for (const input of block.querySelectorAll('input,select,textarea')) { | |
| item[input.name] = input.type === 'checkbox' ? input.checked : input.type === 'number' ? (input.value === '' ? null : Number(input.value)) : input.value; | |
| } | |
| if (kind === 'event' && !item.cancellation_date) item.cancellation_date = null; | |
| return item; | |
| }); | |
| } | |
| $return('review-form').addEventListener('submit', async event => { | |
| event.preventDefault(); | |
| const form = event.currentTarget; | |
| const payload = {ticker:form.elements.ticker.value, title:form.elements.title.value, | |
| source_key:editingReturnDocument?.source_key ?? 'manual:' + crypto.randomUUID(), | |
| source_url:form.elements.source_url.value, published_at:form.elements.published_at.value || null, | |
| evidence:form.elements.review_evidence.value, status:event.submitter?.value || 'active', | |
| supersedes_id:form.elements.supersedes_id.value ? Number(form.elements.supersedes_id.value) : null, | |
| is_withdrawal:form.elements.is_withdrawal.checked, events:readReturnItems('event'), policies:readReturnItems('policy')}; | |
| const buttons = [...form.querySelectorAll('button')]; buttons.forEach(b => b.disabled = true); | |
| try { | |
| const saved = await returnAdminRequest('documents' + (editingReturnDocument ? '/' + editingReturnDocument.id : ''), | |
| {method:editingReturnDocument ? 'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); | |
| await loadReturnAdmin(); editReturnDocument(saved); adminMessage('μ μ₯νμ΅λλ€. λ°μ μ€μΈ μλ£λ μ€ν¬λ¦¬λμ μ¦μ μ μ©λ©λλ€.'); | |
| } catch (error) { adminMessage(error.message); } | |
| finally { buttons.forEach(b => b.disabled = false); } | |
| }); | |
| $return('source-form').addEventListener('submit', async event => { | |
| event.preventDefault(); const form = event.currentTarget; | |
| try { | |
| await returnAdminRequest('sources', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ | |
| ticker:form.elements.ticker.value,url:form.elements.url.value,is_index:form.elements.is_index.value === 'true',enabled:form.elements.enabled.value === 'true'})}); | |
| await loadReturnAdmin(); adminMessage('IR μΆμ²λ₯Ό μ μ₯νμ΅λλ€. λ€μ μμ§ μ€νλΆν° μ μ©λ©λλ€.'); | |
| } catch (error) { adminMessage(error.message); } | |
| }); | |
| $return('new-document').addEventListener('click', () => editReturnDocument()); | |
| $return('add-event').addEventListener('click', () => addReturnItem('event')); | |
| $return('add-policy').addEventListener('click', () => addReturnItem('policy')); | |
| $return('status-filter').addEventListener('change', renderReturnDocuments); | |
| $return('period-filter').addEventListener('change', () => { | |
| const period = $return('period-filter').value; | |
| // History includes approved/excluded records; current work defaults to pending. | |
| $return('status-filter').value = period === 'history' || period === 'all' ? 'all' : period === 'unknown' ? 'reference' : 'active'; | |
| renderReturnDocuments(); | |
| }); | |
| $return('period-current-label').textContent = `μ¬ν΄Β·λ΄λ (${returnReviewYear}Β·${returnReviewYear+1}λ )`; | |
| $return('period-history-label').textContent = `κ³Όκ±° μ΄λ ₯ (${returnReviewYear-1}λ μ΄μ )`; | |
| $return('show-audit').addEventListener('click', async () => { | |
| try { $return('audit-output').textContent = JSON.stringify(await returnAdminRequest(`documents/${editingReturnDocument.id}/audit`),null,2); } | |
| catch (error) { adminMessage(error.message); } | |
| }); | |
| loadReturnAdmin().catch(error => adminMessage(error.message)); | |