working on color customization

This commit is contained in:
2025-09-23 21:59:23 +02:00
parent de432b664b
commit ccde6e3e1d
10 changed files with 120 additions and 52 deletions

View File

@@ -99,6 +99,10 @@ public class Project implements Mappable {
case NAME: name = json.getString(key); break; case NAME: name = json.getString(key); break;
case SHOW_CLOSED: showClosed = json.getBoolean(SHOW_CLOSED); break; case SHOW_CLOSED: showClosed = json.getBoolean(SHOW_CLOSED); break;
case STATUS: status = json.getInt(key); break; case STATUS: status = json.getInt(key); break;
case TAG_COLORS:
tagColors.clear();
json.getJSONObject(TAG_COLORS).toMap().forEach((k,v) -> tagColors.put(k,v.toString()));
break;
default: key = null; default: key = null;
} }
if (key != null) dirtyFields.add(key); if (key != null) dirtyFields.add(key);

View File

@@ -224,7 +224,7 @@
{#if stateList[state]} {#if stateList[state]}
{#each Object.values(stateList[state]).sort((a,b) => a.name.localeCompare(b.name)) as task} {#each Object.values(stateList[state]).sort((a,b) => a.name.localeCompare(b.name)) as task}
{#if !filter || task.name.toLowerCase().includes(filter) || (task.tags && task.tags.filter(tag => tag.toLowerCase().includes(filter)).length)} {#if !filter || task.name.toLowerCase().includes(filter) || (task.tags && task.tags.filter(tag => tag.toLowerCase().includes(filter)).length)}
<Card onclick={e => openTask(task.id)} ondragstart={ev => dragged=task} {task} /> <Card onclick={e => openTask(task.id)} ondragstart={ev => dragged=task} {task} tag_colors={project.tag_colors} />
{/if} {/if}
{/each} {/each}
{/if} {/if}

View File

@@ -1,8 +1,22 @@
<script> <script>
let { onclick, ondragstart, task } = $props(); let { onclick, ondragstart, tag_colors = {}, task } = $props();
function calcStyle(){
if (!task.tags || !tag_colors) return '';
for (let tag of task.tags){
let color = tag_colors[tag.toLowerCase()];
if (color) return `background: ${color}`;
}
return '';
}
let style = $derived.by(calcStyle)
</script> </script>
<div draggable="true" class={`box prio_${task.total_prio} p${Math.floor(task.total_prio/10)*10} p${task.total_prio % 10}`} {onclick} {ondragstart} > <div
draggable="true"
class={`box prio_${task.total_prio} p${Math.floor(task.total_prio/10)*10} p${task.total_prio % 10}`}
{onclick} {ondragstart} style={style}>
<span class="title">{task.name}</span> <span class="title">{task.name}</span>
{#if task.estimated_time} {#if task.estimated_time}
<span class="estimate"> <span class="estimate">

View File

@@ -21,6 +21,7 @@
let showSettings = $state(false); let showSettings = $state(false);
let tasks = $state(null); let tasks = $state(null);
let show_closed = $state(false); let show_closed = $state(false);
let new_color = $state({tag:null,color:'#00aa00'})
let new_state = $state({code:null,name:null}) let new_state = $state({code:null,name:null})
let state_available=$derived(new_state.name && new_state.code && !project.allowed_states[new_state.code]); let state_available=$derived(new_state.name && new_state.code && !project.allowed_states[new_state.code]);
@@ -55,6 +56,11 @@
loadTasks(); loadTasks();
} }
async function dropColor(tag){
delete project.tag_colors[tag];
update({tag_colors:project.tag_colors});
}
async function dropMember(member){ async function dropMember(member){
update({drop_member:member.user.id}); update({drop_member:member.user.id});
} }
@@ -112,6 +118,15 @@
} }
} }
function saveTagColor(){
project.tag_colors[new_color.tag] = new_color.color;
update({tag_colors:project.tag_colors});
}
function toggleSettings(){
showSettings = !showSettings;
}
async function update(data){ async function update(data){
const url = api(`project/${id}`); const url = api(`project/${id}`);
const resp = await fetch(url,{ const resp = await fetch(url,{
@@ -129,11 +144,6 @@
} }
} }
function toggleSettings(){
showSettings = !showSettings;
}
function updatePermission(user_id,permission){ function updatePermission(user_id,permission){
let members = {}; let members = {};
members[user_id] = permission.code; members[user_id] = permission.code;
@@ -188,8 +198,8 @@
<input type="checkbox" bind:checked={project.show_closed} onchange={changeClosed} /> <input type="checkbox" bind:checked={project.show_closed} onchange={changeClosed} />
{t('display_closed_tasks')} {t('display_closed_tasks')}
</label> </label>
<div>{t('members')}</div> <div class="em">{t('members')}</div>
<div> <div class="em">
<PermissionEditor members={project.members} {updatePermission} {addMember} {dropMember} {getCandidates} /> <PermissionEditor members={project.members} {updatePermission} {addMember} {dropMember} {getCandidates} />
</div> </div>
{#if project.allowed_states} {#if project.allowed_states}
@@ -212,7 +222,25 @@
{/if} {/if}
</div> </div>
{/if} {/if}
{/if} <div class="em">
{t('custom_tag_colors')}
<input type="color" bind:value={new_color.color} >
</div>
<div class="em">
<label>
{t('tag_name')}:
<input type="text" bind:value={new_color.tag} />
</label>
<button onclick={saveTagColor}>{t('add_object',{object:t('color')})}</button>
</div>
{#each Object.entries(project.tag_colors) as [k,v]}
<div style="background: {v}">{k}</div>
<div class="em">
<button onclick={e => dropColor(k)}>{t('delete')}</button>
</div>
{/each}
{/if} <!-- settings -->
{#if estimated_time.sum} {#if estimated_time.sum}
<div>{t('estimated_time')}</div> <div>{t('estimated_time')}</div>
<div class="estimated_time">{estimated_time.sum} h</div> <div class="estimated_time">{estimated_time.sum} h</div>

View File

@@ -148,7 +148,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs = select(VALUE).from(TABLE_SETTINGS).where(KEY,equal(colorKey(projectId))).exec(db); rs = select(VALUE).from(TABLE_SETTINGS).where(KEY,equal(colorKey(projectId))).exec(db);
if (rs.next()) { if (rs.next()) {
var map = project.tagColors(); var map = project.tagColors();
new JSONObject(rs.getString(VALUE)).toMap().forEach((k, v) -> map.put(k, v.toString())); new JSONObject(rs.getString(VALUE)).toMap().forEach((k, v) -> map.put(k.toLowerCase(), v.toString()));
} }
rs.close(); rs.close();
return project; return project;
@@ -246,6 +246,10 @@ CREATE TABLE IF NOT EXISTS {0} (
query.execute(db).close(); query.execute(db).close();
prj.clean(MEMBERS); prj.clean(MEMBERS);
} }
if (prj.isDirty(TAG_COLORS)){
replaceInto(TABLE_SETTINGS,KEY,VALUE).values(colorKey(prj.id()),new JSONObject(prj.tagColors()).toString()).execute(db).close();
prj.clean(TAG_COLORS);
}
if (prj.isDirty()){ if (prj.isDirty()){
update(TABLE_PROJECTS).set(NAME,DESCRIPTION,STATUS,COMPANY_ID,SHOW_CLOSED).where(ID,equal(prj.id())).prepare(db) update(TABLE_PROJECTS).set(NAME,DESCRIPTION,STATUS,COMPANY_ID,SHOW_CLOSED).where(ID,equal(prj.id())).prepare(db)
.apply(prj.name(),prj.description(),prj.status(),prj.companyId(),prj.showClosed()) .apply(prj.name(),prj.description(),prj.status(),prj.companyId(),prj.showClosed())

View File

@@ -21,6 +21,7 @@
"client_secret": "Client-Geheimnis", "client_secret": "Client-Geheimnis",
"close_settings": "Einstellungen schließen", "close_settings": "Einstellungen schließen",
"code": "Code", "code": "Code",
"color": "Farbe",
"connect_service": "mit Service verbinden", "connect_service": "mit Service verbinden",
"connected_services": "verbundene Login-Services", "connected_services": "verbundene Login-Services",
"confirm_delete": "Soll '{element}' wirklich gelöscht werden?", "confirm_delete": "Soll '{element}' wirklich gelöscht werden?",
@@ -40,10 +41,11 @@
"CREATE_USERS": "Nutzer anlegen", "CREATE_USERS": "Nutzer anlegen",
"create_pdf": "PDF erzeugen", "create_pdf": "PDF erzeugen",
"created_with": "erzeugt mit {tool} von {producer}", "created_with": "erzeugt mit {tool} von {producer}",
"customer": "Kunde",
"customer_address": "Adresse", "customer_address": "Adresse",
"customer_email": "Emailadresse des Kunden", "customer_email": "Emailadresse des Kunden",
"customer": "Kunde",
"customer_id": "Kundennummer", "customer_id": "Kundennummer",
"custom_tag_colors": "Nutzerdefinierte Tag-Farben",
"data_sent": "Daten übermittelt", "data_sent": "Daten übermittelt",
"date": "Datum", "date": "Datum",
@@ -237,6 +239,7 @@
"succeeding_document": "Nachfolge-Dokument", "succeeding_document": "Nachfolge-Dokument",
"sum_of_records": "Summe der ausgewählten Einträge", "sum_of_records": "Summe der ausgewählten Einträge",
"tag_name": "Tag-Name",
"tag_uses": "Verwendung des Tags „{tag}“", "tag_uses": "Verwendung des Tags „{tag}“",
"tags": "Tags", "tags": "Tags",
"task": "Aufgabe", "task": "Aufgabe",

View File

@@ -21,6 +21,7 @@
"client_secret": "client secret", "client_secret": "client secret",
"close_settings": "close settings", "close_settings": "close settings",
"code": "code", "code": "code",
"color": "color",
"connect_service": "connect with service", "connect_service": "connect with service",
"connected_services": "connected login services", "connected_services": "connected login services",
"confirm_delete": "Really delete '{element}'?", "confirm_delete": "Really delete '{element}'?",
@@ -40,10 +41,11 @@
"CREATE_USERS": "create users", "CREATE_USERS": "create users",
"create_pdf": "create PDF", "create_pdf": "create PDF",
"created_with": "created with {tool} by {producer}", "created_with": "created with {tool} by {producer}",
"customer": "customer",
"customer_address": "address", "customer_address": "address",
"customer_email": "customer email address", "customer_email": "customer email address",
"customer": "customer",
"customer_id": "customer ID", "customer_id": "customer ID",
"custom_tag_colors": "custom tag colors",
"data_sent": "data sent", "data_sent": "data sent",
"date": "date", "date": "date",
@@ -237,6 +239,7 @@
"succeeding_document": "succeeding document", "succeeding_document": "succeeding document",
"sum_of_records": "sum of records", "sum_of_records": "sum of records",
"tag_name": "tag name",
"tag_uses": "usage of tag „{tag}“", "tag_uses": "usage of tag „{tag}“",
"tags": "tags", "tags": "tags",
"task": "task", "task": "task",

View File

@@ -148,7 +148,7 @@ textarea{
} }
.kanban .state_20 .box.p10, .kanban .state_20 .box.p10,
.kanban .state_40 .box.p10{ .kanban .state_40 .box.p10{
background-color: #ffa736; border: 5px solid #ffa736;
} }
.task.p20 .name{ .task.p20 .name{
@@ -156,7 +156,7 @@ textarea{
} }
.kanban .state_20 .box.p20, .kanban .state_20 .box.p20,
.kanban .state_40 .box.p20{ .kanban .state_40 .box.p20{
background-color: #ff8f00; border: 5px solid #ff8f00;
} }
.task.p30 .name{ .task.p30 .name{
@@ -164,7 +164,7 @@ textarea{
} }
.kanban .state_20 .box.p30, .kanban .state_20 .box.p30,
.kanban .state_40 .box.p30{ .kanban .state_40 .box.p30{
background-color: #ff7b06; border: 5px solid #ff7b06;
} }
.task.p40 .name{ .task.p40 .name{
@@ -172,7 +172,7 @@ textarea{
} }
.kanban .state_20 .box.p40, .kanban .state_20 .box.p40,
.kanban .state_40 .box.p40{ .kanban .state_40 .box.p40{
background-color: #ff6306; border: 5px solid #ff6306;
} }
.task.p50 .name{ .task.p50 .name{
@@ -180,7 +180,7 @@ textarea{
} }
.kanban .state_20 .box.p50, .kanban .state_20 .box.p50,
.kanban .state_40 .box.p50{ .kanban .state_40 .box.p50{
background-color: #ff4c06; border: 5px solid #ff4c06;
} }
.task.p60 .name{ .task.p60 .name{
@@ -188,7 +188,7 @@ textarea{
} }
.kanban .state_20 .box.p60, .kanban .state_20 .box.p60,
.kanban .state_40 .box.p60{ .kanban .state_40 .box.p60{
background-color: #ff3506; border: 5px solid #ff3506;
} }
.task.p70 .name{ .task.p70 .name{
@@ -196,7 +196,7 @@ textarea{
} }
.kanban .state_20 .box.p70, .kanban .state_20 .box.p70,
.kanban .state_40 .box.p70{ .kanban .state_40 .box.p70{
background-color: #ff0000; border: 5px solid #ff0000;
} }
.task.p80 .name{ .task.p80 .name{
@@ -204,19 +204,23 @@ textarea{
} }
.kanban .state_20 .box.p80, .kanban .state_20 .box.p80,
.kanban .state_40 .box.p80{ .kanban .state_40 .box.p80{
background-color: #df153b; border: 5px solid #df153b;
} }
.task.p90 .name, .task.p90 .name{
.kanban .state_20 .box.p90,
.kanban .state_40 .box.p90{
background-color: #991c34; background-color: #991c34;
color: #ffff00; color: #ffff00;
} }
.kanban .state_20 .box.p90,
.kanban .state_40 .box.p90{
border: 5px solid #991c34;
}
.task.p100 .name, .task.p100 .name{
.kanban .state_20 .box.p100,
.kanban .state_40 .box.p100{
background-color: #733440; background-color: #733440;
color: #ffff00; color: #ffff00;
}
.kanban .state_20 .box.p100,
.kanban .state_40 .box.p100{
border: 5px solid #733440;
} }

View File

@@ -45,6 +45,10 @@ textarea{
background-color: #333; background-color: #333;
} }
.em {
background: rgba(255, 215, 0, 0.09);
}
.error { .error {
background-color: red; background-color: red;
color: black; color: black;
@@ -150,7 +154,7 @@ textarea{
} }
.kanban .state_20 .box.p10, .kanban .state_20 .box.p10,
.kanban .state_40 .box.p10{ .kanban .state_40 .box.p10{
background-color: #fff066; border: 5px solid #fff066;
} }
.task.p20 .name{ .task.p20 .name{
@@ -158,7 +162,7 @@ textarea{
} }
.kanban .state_20 .box.p20, .kanban .state_20 .box.p20,
.kanban .state_40 .box.p20{ .kanban .state_40 .box.p20{
background-color: #ffe706; border: 5px solid #ffe706;
} }
.task.p30 .name{ .task.p30 .name{
@@ -166,7 +170,7 @@ textarea{
} }
.kanban .state_20 .box.p30, .kanban .state_20 .box.p30,
.kanban .state_40 .box.p30{ .kanban .state_40 .box.p30{
background-color: #ffa906; border: 5px solid #ffa906;
} }
.task.p40 .name{ .task.p40 .name{
@@ -174,7 +178,7 @@ textarea{
} }
.kanban .state_20 .box.p40, .kanban .state_20 .box.p40,
.kanban .state_40 .box.p40{ .kanban .state_40 .box.p40{
background-color: #ff8606; border: 5px solid #ff8606;
} }
.task.p50 .name{ .task.p50 .name{
@@ -182,7 +186,7 @@ textarea{
} }
.kanban .state_20 .box.p50, .kanban .state_20 .box.p50,
.kanban .state_40 .box.p50{ .kanban .state_40 .box.p50{
background-color: #ff4c06; border: 5px solid #ff4c06;
} }
.task.p60 .name{ .task.p60 .name{
@@ -190,7 +194,7 @@ textarea{
} }
.kanban .state_20 .box.p60, .kanban .state_20 .box.p60,
.kanban .state_40 .box.p60{ .kanban .state_40 .box.p60{
background-color: #ff3506; border: 5px solid #ff3506;
} }
.task.p70 .name{ .task.p70 .name{
@@ -198,7 +202,7 @@ textarea{
} }
.kanban .state_20 .box.p70, .kanban .state_20 .box.p70,
.kanban .state_40 .box.p70{ .kanban .state_40 .box.p70{
background-color: #ff0000; border: 5px solid #ff0000;
} }
.task.p80 .name{ .task.p80 .name{
@@ -206,19 +210,23 @@ textarea{
} }
.kanban .state_20 .box.p80, .kanban .state_20 .box.p80,
.kanban .state_40 .box.p80{ .kanban .state_40 .box.p80{
background-color: #df153b; border: 5px solid #df153b;
} }
.task.p90 .name, .task.p90 .name{
.kanban .state_20 .box.p90,
.kanban .state_40 .box.p90{
background-color: #991c34; background-color: #991c34;
color: #ffff00; color: #ffff00;
} }
.kanban .state_20 .box.p90,
.kanban .state_40 .box.p90{
border: 5px solid #991c34;
}
.task.p100 .name, .task.p100 .name{
.kanban .state_20 .box.p100,
.kanban .state_40 .box.p100{
background-color: #733440; background-color: #733440;
color: #ffff00; color: #ffff00;
}
.kanban .state_20 .box.p100,
.kanban .state_40 .box.p100{
border: 5px solid #733440;
} }

View File

@@ -142,7 +142,7 @@ textarea{
} }
.kanban .state_20 .box.p10, .kanban .state_20 .box.p10,
.kanban .state_40 .box.p10{ .kanban .state_40 .box.p10{
background-color: #c9fbb2; border: 5px solid #c9fbb2;
} }
.task.p20 .name{ .task.p20 .name{
@@ -150,7 +150,7 @@ textarea{
} }
.kanban .state_20 .box.p20, .kanban .state_20 .box.p20,
.kanban .state_40 .box.p20{ .kanban .state_40 .box.p20{
background-color: #cbff57; border: 5px solid #cbff57;
} }
.task.p30 .name{ .task.p30 .name{
@@ -158,7 +158,7 @@ textarea{
} }
.kanban .state_20 .box.p30, .kanban .state_20 .box.p30,
.kanban .state_40 .box.p30{ .kanban .state_40 .box.p30{
background-color: #dfff44; border: 5px solid #dfff44;
} }
.task.p40 .name{ .task.p40 .name{
@@ -166,7 +166,7 @@ textarea{
} }
.kanban .state_20 .box.p40, .kanban .state_20 .box.p40,
.kanban .state_40 .box.p40{ .kanban .state_40 .box.p40{
background-color: #f8ff29; border: 5px solid #f8ff29;
} }
.task.p50 .name{ .task.p50 .name{
@@ -174,7 +174,7 @@ textarea{
} }
.kanban .state_20 .box.p50, .kanban .state_20 .box.p50,
.kanban .state_40 .box.p50{ .kanban .state_40 .box.p50{
background-color: #ffdb1b; border: 5px solid #ffdb1b;
} }
.task.p60 .name{ .task.p60 .name{
@@ -182,7 +182,7 @@ textarea{
} }
.kanban .state_20 .box.p60, .kanban .state_20 .box.p60,
.kanban .state_40 .box.p60{ .kanban .state_40 .box.p60{
background-color: #ff9309; border: 5px solid #ff9309;
} }
.task.p70 .name{ .task.p70 .name{
@@ -190,7 +190,7 @@ textarea{
} }
.kanban .state_20 .box.p70, .kanban .state_20 .box.p70,
.kanban .state_40 .box.p70{ .kanban .state_40 .box.p70{
background-color: #ff6c00; border: 5px solid #ff6c00;
} }
.task.p80 .name{ .task.p80 .name{
@@ -198,7 +198,7 @@ textarea{
} }
.kanban .state_20 .box.p80, .kanban .state_20 .box.p80,
.kanban .state_40 .box.p80{ .kanban .state_40 .box.p80{
background-color: #ff3c00; border: 5px solid #ff3c00;
} }
.task.p90 .name{ .task.p90 .name{
@@ -206,7 +206,7 @@ textarea{
} }
.kanban .state_20 .box.p90, .kanban .state_20 .box.p90,
.kanban .state_40 .box.p90{ .kanban .state_40 .box.p90{
background-color: #ff0000; border: 5px solid #ff0000;
} }
.task.p100 .name{ .task.p100 .name{
@@ -214,5 +214,5 @@ textarea{
} }
.kanban .state_20 .box.p100, .kanban .state_20 .box.p100,
.kanban .state_40 .box.p100{ .kanban .state_40 .box.p100{
background-color: #ff0048; border: 5px solid #ff0048;
} }