UNPKG

gentelella

Version:

Gentelella v4 — free admin template. 60 pages, 20 chart variants, fully interactive inbox & kanban, live theme generator, component playground, PWA-ready. Vite 8, vanilla JS, no Bootstrap, no jQuery.

1 lines 11.2 kB
import{t as e}from"./toast-DgCSlJPv.js";import{n as t}from"./modal-MTuCfURV.js";var a=[{id:"todo",title:"To do",color:"var(--text-muted)"},{id:"doing",title:"In progress",color:"var(--blue)"},{id:"review",title:"Review",color:"var(--yellow)"},{id:"done",title:"Done",color:"var(--green)"}],n=[{id:"design",text:"Design",color:"purple"},{id:"eng",text:"Eng",color:"blue"},{id:"pm",text:"PM",color:"green"},{id:"bug",text:"Bug",color:"red"},{id:"urgent",text:"Urgent",color:"red"},{id:"docs",text:"Docs",color:"yellow"}],o={SK:{name:"Sarah Kowalski",color:"azure"},MR:{name:"Michael Reyes",color:"purple"},EW:{name:"Emily Wang",color:"yellow"},MK:{name:"Mark Kim",color:"red"},LP:{name:"Lina Park",color:"green"},DR:{name:"Diego Reyes",color:"blue"},YT:{name:"Yuki Tanaka",color:"primary"},TH:{name:"Tom Hardy",color:"purple"}},i={primary:"var(--primary)",azure:"var(--azure)",blue:"var(--blue)",purple:"var(--purple)",yellow:"var(--yellow)",red:"var(--red)",green:"var(--green)"},r=1,l=[{id:r++,col:"todo",title:"Define onboarding email sequence",desc:"Three-message welcome flow with day 1, 3, 7 cadence",labels:["pm"],assignees:["EW"],due:"May 02",priority:"medium"},{id:r++,col:"todo",title:"Refresh icon system",desc:"Move from heroicons to a custom set with consistent stroke",labels:["design"],assignees:["SK"],due:"May 05",priority:"low"},{id:r++,col:"todo",title:"Audit a11y on form pages",desc:"",labels:["eng","docs"],assignees:["MR","TH"],due:"May 08",priority:"medium"},{id:r++,col:"todo",title:"Plan Q3 OKRs",desc:"Workshop with leads to define Q3 outcomes",labels:["pm"],assignees:["EW"],due:"May 15",priority:"low"},{id:r++,col:"doing",title:"Implement drag-and-drop kanban",desc:"Use HTML5 drag API, no library",labels:["eng"],assignees:["MR"],due:"Apr 30",priority:"high"},{id:r++,col:"doing",title:"Design product detail page",desc:"Gallery + variants + reviews + related",labels:["design"],assignees:["SK"],due:"Apr 30",priority:"high"},{id:r++,col:"doing",title:"Write FAQ content",desc:"15 articles across 5 categories",labels:["docs"],assignees:["EW"],due:"May 01",priority:"medium"},{id:r++,col:"review",title:"Fix DataTables sort indicators",desc:"Replace empty span with proper chevron SVG",labels:["bug","eng"],assignees:["MR"],due:"Apr 29",priority:"medium"},{id:r++,col:"review",title:"Notifications dropdown",desc:"Bell button → panel with sample notifications",labels:["eng"],assignees:["TH"],due:"Apr 29",priority:"medium"},{id:r++,col:"done",title:"Build chat page",desc:"",labels:["eng"],assignees:["MR"],due:"Apr 28",priority:"high"},{id:r++,col:"done",title:"Build settings page",desc:"",labels:["eng","design"],assignees:["SK","TH"],due:"Apr 28",priority:"high"},{id:r++,col:"done",title:"Migrate to Vite 8",desc:"",labels:["eng"],assignees:["MR"],due:"Apr 25",priority:"medium"},{id:r++,col:"done",title:"Add dark mode",desc:"Pre-paint script, token overrides, ECharts theme observer",labels:["eng","design"],assignees:["MR","SK"],due:"Apr 25",priority:"high"}];function s(e){return String(e).replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[e]))}function d(e){const t=e.labels.map(e=>{const t=function(e){return n.find(t=>t.id===e)}(e);return t?`<span class="kanban-label kanban-label-${t.color}">${t.text}</span>`:""}).join(""),a=e.assignees.map(e=>{const t=o[e];return t?`<span class="kanban-avatar" style="background:${i[t.color]}" title="${s(t.name)}">${e}</span>`:""}).join(""),r="high"===e.priority?'<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="var(--red)" stroke-width="2"><path d="M3 12V4l5 4 5-4v8"/></svg>':"low"===e.priority?'<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="var(--text-muted)" stroke-width="2"><path d="M3 4v8l5-4 5 4V4"/></svg>':"";return`\n <article class="kanban-card" draggable="true" data-id="${e.id}">\n ${t?`<div class="kanban-card-labels">${t}</div>`:""}\n <div class="kanban-card-title">${s(e.title)}</div>\n ${e.desc?`<div class="kanban-card-desc">${s(e.desc)}</div>`:""}\n <div class="kanban-card-foot">\n <div class="kanban-card-meta">\n ${r}\n ${e.due?`<span class="due-date">${s(e.due)}</span>`:""}\n </div>\n <div class="kanban-card-avatars">${a}</div>\n </div>\n </article>\n `}function c(e){const t=l.filter(t=>t.col===e.id);return`\n <section class="kanban-column" data-col="${e.id}">\n <header class="kanban-column-head">\n <span class="dot" style="background:${e.color}"></span>\n <span class="title">${s(e.title)}</span>\n <span class="count">${t.length}</span>\n <button type="button" class="kanban-add" data-add-col="${e.id}" aria-label="Add card to ${e.title}">+</button>\n </header>\n <div class="kanban-column-body" data-drop="${e.id}">\n ${t.map(d).join("")}\n </div>\n <button type="button" class="kanban-column-foot" data-add-col="${e.id}">+ Add card</button>\n </section>\n `}function u(){document.getElementById("kanban-board").innerHTML=a.map(c).join("")}var p=null;function g(t){t.addEventListener("dragstart",e=>{const t=e.target.closest(".kanban-card");t&&(p=parseInt(t.dataset.id,10),t.classList.add("dragging"),e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",String(p)))}),t.addEventListener("dragend",e=>{const t=e.target.closest(".kanban-card");t&&t.classList.remove("dragging"),document.querySelectorAll(".kanban-column-body.drop-target").forEach(e=>e.classList.remove("drop-target")),p=null}),t.addEventListener("dragover",e=>{const t=e.target.closest("[data-drop]");if(!t)return;e.preventDefault(),e.dataTransfer.dropEffect="move",t.classList.add("drop-target");const a=document.querySelector(".kanban-card.dragging");if(!a)return;const n=(o=e.clientY,[...t.querySelectorAll(".kanban-card:not(.dragging)")].reduce((e,t)=>{const a=t.getBoundingClientRect(),n=o-a.top-a.height/2;return 0>n&&n>e.offset?{offset:n,element:t}:e},{offset:-1/0,element:null}).element);var o;null===n?t.appendChild(a):t.insertBefore(a,n)}),t.addEventListener("dragleave",e=>{const t=e.target.closest("[data-drop]");t&&!t.contains(e.relatedTarget)&&t.classList.remove("drop-target")}),t.addEventListener("drop",n=>{const o=n.target.closest("[data-drop]");if(!o||null===p)return;n.preventDefault(),o.classList.remove("drop-target");const i=o.dataset.drop,r=l.find(e=>e.id===p);r&&r.col!==i?(r.col=i,m(),u(),g(t),e(`Moved to ${a.find(e=>e.id===i)?.title||i}`)):m()})}function m(){const e=[];a.forEach(t=>{const a=document.querySelector(`[data-drop="${t.id}"]`);a&&a.querySelectorAll(".kanban-card").forEach(t=>{const a=parseInt(t.dataset.id,10),n=l.find(e=>e.id===a);n&&e.push(n)})}),l.length=0,e.forEach(e=>l.push(e))}function b(d){const c=!d,p=c?{id:r++,col:"todo",title:"",desc:"",labels:[],assignees:[],due:"",priority:"medium"}:l.find(e=>e.id===d);if(!p)return;const m=n.map(e=>`\n <button type="button" class="chip${p.labels.includes(e.id)?" active":""}" data-label="${e.id}">${e.text}</button>\n `).join(""),b=Object.entries(o).map(([e,t])=>`\n <button type="button" class="kanban-assignee-chip${p.assignees.includes(e)?" active":""}" data-assignee="${e}" title="${s(t.name)}">\n <span class="av" style="background:${i[t.color]}">${e}</span>\n <span>${s(t.name)}</span>\n </button>\n `).join(""),v=`\n <form class="modal-form" novalidate>\n <div class="modal-form-row">\n <label for="kc-title">Title</label>\n <input type="text" id="kc-title" name="title" value="${s(p.title)}" placeholder="Task title" autocomplete="off" required>\n </div>\n <div class="modal-form-row">\n <label for="kc-desc">Description</label>\n <textarea id="kc-desc" name="desc" rows="3" placeholder="Optional notes…">${s(p.desc)}</textarea>\n </div>\n <div class="modal-form-row">\n <label>Labels</label>\n <div class="kanban-chip-group" data-group="labels">${m}</div>\n </div>\n <div class="modal-form-row">\n <label>Assignees</label>\n <div class="kanban-chip-group" data-group="assignees">${b}</div>\n </div>\n <div class="modal-form-row" style="display:grid;grid-template-columns:1fr 1fr;gap:12px">\n <div>\n <label for="kc-due">Due date</label>\n <input type="text" id="kc-due" name="due" value="${s(p.due)}" placeholder="May 15">\n </div>\n <div>\n <label for="kc-priority">Priority</label>\n <select id="kc-priority" name="priority">\n <option value="low">Low</option>\n <option value="medium">Medium</option>\n <option value="high">High</option>\n </select>\n </div>\n </div>\n <div class="modal-form-row">\n <label for="kc-col">Column</label>\n <select id="kc-col" name="col">\n ${a.map(e=>`<option value="${e.id}">${e.title}</option>`).join("")}\n </select>\n </div>\n </form>\n `,y=[];c||y.push({label:"Delete",variant:"outline",closeOnAction:!1,action:()=>{const t=l.findIndex(e=>e.id===d);if(t>-1){const a=l[t];l.splice(t,1),u(),g(document.getElementById("kanban-board")),e(`Deleted: ${a.title}`)}return!0}}),y.push({label:"Cancel",variant:"outline"}),y.push({label:c?"Create":"Save",variant:"primary",action:({body:t})=>{const a=t.querySelector("form"),n=new FormData(a),o=(n.get("title")||"").toString().trim();return o?(p.title=o,p.desc=(n.get("desc")||"").toString().trim(),p.due=(n.get("due")||"").toString().trim(),p.priority=(n.get("priority")||"medium").toString(),p.col=(n.get("col")||"todo").toString(),p.labels=Array.from(t.querySelectorAll('[data-group="labels"] .chip.active')).map(e=>e.dataset.label),p.assignees=Array.from(t.querySelectorAll('[data-group="assignees"] .kanban-assignee-chip.active')).map(e=>e.dataset.assignee),c&&l.push(p),u(),g(document.getElementById("kanban-board")),e(c?`Created: ${o}`:`Saved: ${o}`,{variant:"success"}),!0):(e("Title is required",{variant:"warning"}),!1)}});const{dialog:f}=t({title:c?"New card":"Edit card",body:v,actions:y,size:"lg"});f.querySelector("#kc-col").value=p.col,f.querySelector("#kc-priority").value=p.priority||"medium",f.querySelectorAll(".kanban-chip-group .chip, .kanban-chip-group .kanban-assignee-chip").forEach(e=>{e.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation(),e.classList.toggle("active")})})}function v(){const e=document.getElementById("kanban-board");e&&(u(),g(e),e.addEventListener("click",e=>{const t=e.target.closest("[data-add-col]");if(t){e.stopPropagation();const a=t.dataset.addCol;b(null);const n=document.querySelector(".modal-backdrop .modal-body #kc-col");return void(n&&(n.value=a))}const a=e.target.closest(".kanban-card");a&&!a.classList.contains("dragging")&&b(parseInt(a.dataset.id,10))}),document.getElementById("kanban-filter")?.addEventListener("input",e=>{const t=e.target.value.trim().toLowerCase();document.querySelectorAll(".kanban-card").forEach(e=>{const a=parseInt(e.dataset.id,10),n=l.find(e=>e.id===a);if(!n)return;const o=!t||n.title.toLowerCase().includes(t)||n.desc.toLowerCase().includes(t);e.style.display=o?"":"none"})}))}export{v as initKanban};