Merge pull request #2 from aimanzahar/design_fix

Design fix
This commit is contained in:
2025-09-27 14:07:09 +08:00
committed by GitHub
6 changed files with 310 additions and 47 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -446,10 +446,10 @@ const cycleStatus = async (reportId) => {
<h3>{t('dashboard.filters') || 'Filters'}</h3>
<div className="filter-group">
<div className="row space-between"><strong>{t('filter.category') || 'Category'}</strong></div>
<div className="row"><strong>{t('filter.category') || 'Category'}</strong></div>
<div className="checkbox-row" aria-label="categories">
{CATEGORY_LIST.map(cat=>(
<label key={cat} style={{display:'flex',alignItems:'center',gap:8}}>
<label key={cat}>
<input type="checkbox"
checked={formCategories.has(cat)}
onChange={()=> toggleSet(setFormCategories, formCategories, cat)}
@@ -461,7 +461,7 @@ const cycleStatus = async (reportId) => {
</div>
<div className="filter-group">
<div className="row space-between"><strong>{t('filter.severity') || 'Severity'}</strong></div>
<div className="row"><strong>{t('filter.severity') || 'Severity'}</strong></div>
<div className="multi-select">
{SEVERITIES.map(s=>(
<button key={s} className={`chip severity-${s}`} onClick={()=> toggleSet(setFormSeverities, formSeverities, s)} aria-pressed={formSeverities.has(s)}>
@@ -472,7 +472,7 @@ const cycleStatus = async (reportId) => {
</div>
<div className="filter-group">
<div className="row space-between"><strong>{t('filter.status') || 'Status'}</strong></div>
<div className="row"><strong>{t('filter.status') || 'Status'}</strong></div>
<div className="multi-select">
{STATUSES.map(s=>(
<button key={s} className={`chip status-${s}`} onClick={()=> toggleSet(setFormStatuses, formStatuses, s)} aria-pressed={formStatuses.has(s)}>
@@ -483,20 +483,20 @@ const cycleStatus = async (reportId) => {
</div>
<div className="filter-group">
<div className="row space-between"><strong>{t('filter.dateRange') || 'Date Range'}</strong></div>
<div style={{display:'flex',gap:8,marginTop:8}}>
<div style={{display:'flex',flexDirection:'column'}}>
<label style={{fontSize:12}}>{t('filter.dateFrom') || 'From'}</label>
<div className="row"><strong>{t('filter.dateRange') || 'Date Range'}</strong></div>
<div className="date-inputs">
<div className="date-input-group">
<label>{t('filter.dateFrom') || 'From'}</label>
<input type="date" value={formFrom} onChange={e=>setFormFrom(e.target.value)} />
</div>
<div style={{display:'flex',flexDirection:'column'}}>
<label style={{fontSize:12}}>{t('filter.dateTo') || 'To'}</label>
<div className="date-input-group">
<label>{t('filter.dateTo') || 'To'}</label>
<input type="date" value={formTo} onChange={e=>setFormTo(e.target.value)} />
</div>
</div>
</div>
<div style={{display:'flex',gap:8,marginTop:12}}>
<div className="button-group">
<button className="btn" onClick={applyFilters}>{t('btn.apply') || 'Apply'}</button>
<button className="btn secondary" onClick={resetFilters}>{t('btn.reset') || 'Reset'}</button>
</div>
@@ -535,11 +535,11 @@ const cycleStatus = async (reportId) => {
<span className="time-ago">{dayjs(r.createdAt).fromNow()}</span>
</div>
</div>
<div className="item-actions" style={{display:'flex',flexDirection:'column',gap:8,alignItems:'flex-end'}}>
<select value={r.status} onChange={(e)=> updateTicketStatus(r.id, e.target.value)}>
<div className="item-actions" style={{display:'flex',flexDirection:'row',gap:8,alignItems:'center',justifyContent:'flex-end',minWidth:'120px'}}>
<select value={r.status} onChange={(e)=> updateTicketStatus(r.id, e.target.value)} style={{fontSize:'12px',padding:'4px 8px'}}>
{availableStatuses.map(s => <option key={s} value={s}>{t(`status.${s}`) || s}</option>)}
</select>
<button className="btn ghost" onClick={()=> { setSelected(r); }}>{t('btn.view') || 'View'}</button>
<button className="btn ghost" onClick={()=> { setSelected(r); }} style={{fontSize:'12px',padding:'4px 12px',whiteSpace:'nowrap'}}>{t('btn.view') || 'View'}</button>
</div>
</div>
))}
@@ -585,7 +585,7 @@ const cycleStatus = async (reportId) => {
<div className="drawer-body">
<p style={{marginTop:8}}><strong>{t('drawer.details') || 'Details'}</strong></p>
{selected.notes ? <p>{selected.notes}</p> : <p style={{opacity:0.7}}>{t('drawer.noNotes') || 'No additional notes'}</p>}
<p><strong>{t('label.submittedBy') || 'Submitted by'}:</strong> {selected.userName || (t('label.guest') || 'Guest')}</p>
<p><strong>{t('label.submittedBy') || 'Submitted by'}:</strong> {selected.userName && !selected.userName.startsWith('Guest-') ? selected.userName : (t('label.anonymous') || 'Anonymous User')}</p>
<p><strong>{t('label.place') || 'Place'}:</strong> {selected.address ? selected.address : `${selected.location.lat.toFixed(5)}, ${selected.location.lng.toFixed(5)}`}</p>
<p><strong>{t('label.location') || 'Location'}:</strong> {selected.location.lat.toFixed(5)}, {selected.location.lng.toFixed(5)}</p>
<p><strong>{t('label.createdAt') || 'Created'}:</strong> {dayjs(selected.createdAt).format('YYYY-MM-DD HH:mm')}</p>

View File

@@ -13,6 +13,9 @@
"label.language": "Language",
"label.location": "Location",
"label.createdAt": "Created At",
"label.submittedBy": "Submitted by",
"label.place": "Place",
"label.anonymous": "Anonymous User",
"filter.category": "Category",
"filter.severity": "Severity",
"filter.status": "Status",

View File

@@ -13,6 +13,9 @@
"label.language": "Bahasa",
"label.location": "Lokasi",
"label.createdAt": "Dicipta Pada",
"label.submittedBy": "Dihantar oleh",
"label.place": "Tempat",
"label.anonymous": "Pengguna Tanpa Nama",
"filter.category": "Kategori",
"filter.severity": "Keparahan",
"filter.status": "Status",

View File

@@ -30,29 +30,59 @@ body{
display:flex;
align-items:center;
justify-content:space-between;
padding:0 16px;
padding:0 20px;
background:var(--panel);
border-bottom:1px solid #e6eef3;
box-shadow: none;
border-bottom:1px solid #e5e7eb;
box-shadow:0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
z-index:100;
}
.brand{font-weight:700;font-size:18px;color:#111827}
.lang-toggle select{padding:6px;border-radius:6px;border:1px solid #e6eef3;background:white}
.brand{
font-weight:700;
font-size:20px;
color:#111827;
background:linear-gradient(135deg, var(--accent), #0d9488);
-webkit-background-clip:text;
-webkit-text-fill-color:transparent;
background-clip:text;
}
.lang-toggle{
display:flex;
align-items:center;
gap:8px;
}
.lang-toggle select{
padding:8px 12px;
border:1px solid #d1d5db;
border-radius:6px;
background:white;
font-size:14px;
color:#374151;
cursor:pointer;
transition:border-color 0.2s ease;
}
.lang-toggle select:focus{
outline:none;
border-color:var(--accent);
box-shadow:0 0 0 3px rgba(14,165,164,0.1);
}
.app-root{height:100vh;display:flex;flex-direction:column}
.container{
height:calc(100vh - 56px);
display:flex;
flex-direction:column;
gap:8px;
padding:12px;
gap:16px;
padding:16px;
max-width:1400px;
margin:0 auto;
width:100%;
}
/* main area */
.main{
display:grid;
grid-template-columns:300px 1fr 340px;
grid-template-columns:300px 1fr 400px;
gap:12px;
align-items:stretch;
flex:1;
@@ -61,35 +91,121 @@ body{
/* panels */
.panel{
background:var(--panel);
border-radius:8px;
box-shadow:var(--shadow);
padding:12px;
border-radius:12px;
box-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding:20px;
display:flex;
flex-direction:column;
min-height:0;
border:1px solid rgba(0,0,0,0.05);
}
.filters h3{margin:0 0 8px 0}
.filter-group{margin-bottom:12px}
.checkbox-row{display:flex;flex-direction:column;gap:6px;max-height:220px;overflow:auto;padding-right:6px}
.checkbox-row label{font-size:13px;color:#111827}
.filters h3{
margin:0 0 20px 0;
font-size:18px;
font-weight:600;
color:#111827;
border-bottom:2px solid #f3f4f6;
padding-bottom:12px;
}
.filter-group{
margin-bottom:24px;
padding:16px;
background:#fafbfc;
border-radius:8px;
border:1px solid #e5e7eb;
overflow:hidden;
}
.filter-group:last-child{margin-bottom:0;}
.checkbox-row{
display:flex;
flex-direction:column;
gap:12px;
max-height:240px;
overflow-y:auto;
padding-right:8px;
}
.checkbox-row label{
font-size:14px;
color:#374151;
font-weight:500;
display:flex;
align-items:center;
gap:10px;
padding:8px;
border-radius:6px;
transition:background-color 0.2s ease;
cursor:pointer;
}
.checkbox-row label:hover{
background-color:#f3f4f6;
}
/* chips/buttons */
.btn{
background:var(--accent);
color:white;
border:none;
padding:8px 12px;
border-radius:6px;
padding:10px 20px;
border-radius:8px;
cursor:pointer;
font-weight:600;
font-size:14px;
transition:all 0.2s ease;
box-shadow:0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
.btn:hover{
background:#0d9488;
transform:translateY(-1px);
box-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.btn.secondary{
background:#f8fafc;
color:#475569;
border:1px solid #e2e8f0;
}
.btn.secondary:hover{
background:#f1f5f9;
border-color:#cbd5e1;
}
.btn.ghost{
background:transparent;
border:1px solid #e2e8f0;
color:#475569;
padding:8px 16px;
}
.btn.ghost:hover{
background:#f8fafc;
border-color:#cbd5e1;
}
.btn:focus{
outline:2px solid rgba(14,165,164,0.25);
outline-offset:2px;
}
.btn.secondary{background:#f1f5f9;color:#0f172a}
.btn.ghost{background:transparent;border:1px solid #e6eef3;color:#0f172a;padding:6px 10px}
.btn:focus{outline:2px solid rgba(14,165,164,0.25)}
.multi-select{display:flex;gap:8px;flex-wrap:wrap}
.chip{display:inline-block;padding:4px 8px;border-radius:14px;font-size:13px;color:white}
.multi-select{
display:flex;
gap:10px;
flex-wrap:wrap;
margin-top:12px;
}
.chip{
display:inline-flex;
align-items:center;
padding:6px 12px;
border-radius:20px;
font-size:13px;
font-weight:500;
color:white;
transition:all 0.2s ease;
cursor:pointer;
border:none;
box-shadow:0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
.chip:hover{
transform:translateY(-1px);
box-shadow:0 2px 4px 0 rgba(0, 0, 0, 0.15);
}
.chip.severity-high{background:var(--severity-high)}
.chip.severity-medium{background:var(--severity-medium)}
.chip.severity-low{background:var(--severity-low)}
@@ -98,8 +214,18 @@ body{
.chip.status-fixed{background:var(--status-fixed)}
/* severity buttons in filter */
button.chip{border:none;cursor:pointer;opacity:0.95}
button.chip[aria-pressed="false"]{opacity:0.55;filter:grayscale(0.15)}
button.chip{
opacity:0.9;
transition:opacity 0.2s ease, filter 0.2s ease;
}
button.chip[aria-pressed="false"]{
opacity:0.4;
filter:grayscale(0.3) saturate(0.5);
}
button.chip[aria-pressed="true"]{
opacity:1;
filter:none;
}
/* map panel */
.map-panel{position:relative;min-height:0;height:100%;padding:0;overflow:hidden}
@@ -115,11 +241,11 @@ button.chip[aria-pressed="false"]{opacity:0.55;filter:grayscale(0.15)}
.map-panel.no-reports .map-empty{display:flex}
/* queue list */
.queue-list{display:flex;flex-direction:column;gap:8px;overflow:auto;padding-right:6px}
.queue-item{display:flex;align-items:center;gap:12px;padding:8px;border-radius:8px;border:1px solid #eef2f7;background:linear-gradient(180deg,#fff,#fbfdff)}
.thumb{width:56px;height:56px;border-radius:6px;background:linear-gradient(180deg,#eef2ff,#fff);display:flex;align-items:center;justify-content:center;color:#0f172a;font-weight:700}
.item-main{flex:1;min-width:0}
.item-title{font-weight:600;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}
.queue-list{display:flex;flex-direction:column;gap:8px;overflow:auto;padding-right:6px;max-height:calc(100vh - 200px)}
.queue-item{display:flex;align-items:center;gap:12px;padding:8px;border-radius:8px;border:1px solid #eef2f7;background:linear-gradient(180deg,#fff,#fbfdff);min-height:48px}
.thumb{width:56px;height:56px;border-radius:6px;background:linear-gradient(180deg,#eef2ff,#fff);display:flex;align-items:center;justify-content:center;color:#0f172a;font-weight:700;flex-shrink:0}
.item-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
.item-title{font-weight:600;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;font-size:14px}
.item-title.clickable{
cursor:pointer;
transition:all 0.2s ease;
@@ -133,8 +259,18 @@ button.chip[aria-pressed="false"]{opacity:0.55;filter:grayscale(0.15)}
transform:translateY(-1px);
box-shadow:0 2px 8px rgba(14,165,164,0.15);
}
.item-meta{display:flex;gap:8px;align-items:center;margin-top:6px;font-size:12px;color:var(--muted)}
.item-actions{display:flex;align-items:center}
.item-meta{display:flex;gap:8px;align-items:center;margin-top:2px;font-size:12px;color:var(--muted);flex-wrap:wrap}
.item-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}
/* Ensure proper spacing in queue items */
.queue-item > * {
flex-shrink: 0;
}
.queue-item .item-main {
flex-shrink: 1;
min-width: 0;
}
/* drawer */
.drawer{
@@ -168,6 +304,73 @@ button.chip[aria-pressed="false"]{opacity:0.55;filter:grayscale(0.15)}
/* marker custom */
.leaflet-container .custom-marker{display:flex;align-items:center;justify-content:center}
/* date range styling */
.filter-group .row {
display:flex;
align-items:center;
justify-content:space-between;
margin-bottom:12px;
}
.filter-group .row strong {
font-size:14px;
font-weight:600;
color:#111827;
}
.filter-group .date-inputs {
display:flex;
gap:8px;
margin-top:8px;
align-items:flex-end;
}
.filter-group .date-input-group {
flex:1;
min-width:0;
display:flex;
flex-direction:column;
}
.filter-group .date-input-group label {
font-size:12px;
font-weight:500;
color:#6b7280;
margin-bottom:4px;
white-space:nowrap;
}
.filter-group .date-input-group input[type="date"] {
padding:6px 8px;
border:1px solid #d1d5db;
border-radius:6px;
font-size:14px;
background:white;
transition:border-color 0.2s ease, box-shadow 0.2s ease;
width:100%;
box-sizing:border-box;
}
.filter-group .date-input-group input[type="date"]:focus {
outline:none;
border-color:var(--accent);
box-shadow:0 0 0 3px rgba(14,165,164,0.1);
}
/* button group styling */
.filter-group .button-group {
display:flex;
gap:8px;
margin-top:16px;
justify-content:flex-end;
}
.filter-group .button-group .btn {
padding:8px 16px;
font-size:13px;
min-width:60px;
}
/* small screens */
@media (max-width:900px){
.main{grid-template-columns:1fr;grid-auto-rows:auto}
@@ -176,7 +379,61 @@ button.chip[aria-pressed="false"]{opacity:0.55;filter:grayscale(0.15)}
.header{padding:8px 12px}
.filters{order:2}
.map-panel{order:1}
.panel{padding:10px}
.panel{padding:16px}
.filter-group .date-inputs {
flex-direction:column;
gap:12px;
}
.filter-group .date-input-group {
min-width:auto;
}
.filter-group .button-group {
flex-direction:column;
gap:8px;
}
.queue-item .item-actions {
flex-direction:column;
align-items:stretch;
gap:6px;
min-width:auto;
}
.queue-item .item-actions select,
.queue-item .item-actions button {
width:100%;
text-align:center;
}
}
/* medium screens - adjust for better button layout */
@media (max-width:1200px) and (min-width:901px){
.main{grid-template-columns:280px 1fr 360px}
}
/* footer styling */
.footer{
background:var(--panel);
border-radius:12px;
padding:16px 20px;
display:flex;
justify-content:space-between;
align-items:center;
border:1px solid rgba(0,0,0,0.05);
box-shadow:0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
.stats{
display:flex;
align-items:center;
gap:16px;
font-size:14px;
}
.stats > div{
display:flex;
align-items:center;
gap:6px;
color:#374151;
font-weight:500;
}
/* accessibility tweaks */