Compare commits
133 Commits
feature/ta
...
bugfix/pro
| Author | SHA1 | Date | |
|---|---|---|---|
| d7c32ef69a | |||
| dba2657894 | |||
| 03b9a472c3 | |||
| 9f3631ecfd | |||
| f9bb8def03 | |||
| 229a6f8e5a | |||
| 20d46ea135 | |||
| 30ebe3f4e7 | |||
| 87b81756cd | |||
| b3925bb2b9 | |||
| 1eaff233d6 | |||
| a2e2643020 | |||
| 62981b22b5 | |||
| d936c08d35 | |||
| 69beabfbc8 | |||
| 7349d301dd | |||
| 24c079a0ed | |||
| 40bdad12de | |||
| 697d7b01e5 | |||
| b7219ef6ec | |||
| 7cccb7482b | |||
| dde23dc9c3 | |||
| c4351d3007 | |||
| 4a5ae7e1ad | |||
| a37a57ab3f | |||
| 6e73a71082 | |||
| d847fbecbb | |||
| f455d748f6 | |||
| 83c173a108 | |||
| 4f7cbd9f22 | |||
| 702684666c | |||
| db95cdbb58 | |||
| 99b33791ef | |||
| a72d556a36 | |||
| f7d6f2936a | |||
| fb4fcc53de | |||
| 7f39d71f4a | |||
| 4ebb5d54d3 | |||
| e86e9289ed | |||
| 604814e057 | |||
| 4964082c2e | |||
| a5e2c69afb | |||
| 494ba3ff41 | |||
| 53fe0db2c1 | |||
| 6843bbb475 | |||
| 79924094e4 | |||
| 4f4174df8d | |||
| f052b62a30 | |||
| b7c2bcb741 | |||
| c5099e41f8 | |||
| f08f7a0852 | |||
| 2a134b72b7 | |||
| 3b6e84d1af | |||
| 75441c3260 | |||
| fd3d01b905 | |||
| efd0773e5c | |||
| b2241c3504 | |||
| 7f48fc12c8 | |||
| 3ba49ed87f | |||
| 018d67e959 | |||
| f2065c5be6 | |||
| a921fac87e | |||
| 80a471d904 | |||
| 09a6a809b2 | |||
| 6656b67ae7 | |||
| b050f06467 | |||
| 06189dff5b | |||
| 31bb075be7 | |||
| f6f9a7c18b | |||
| 6fe1c82e05 | |||
| 3070f67274 | |||
| b238b21a6a | |||
| 9221449ecf | |||
| 866ce71b30 | |||
| 2088cd608d | |||
| 752a487cb0 | |||
| e48538727f | |||
| cdb8814f20 | |||
| b56979881f | |||
| 59d54b734b | |||
| c25c342d15 | |||
| 6c183d0467 | |||
| 66ea13186a | |||
| 710ac289ec | |||
| dc5044243b | |||
| b4e0b5ad6a | |||
| 6f3338a95e | |||
| 28a08670d7 | |||
| 5992bff658 | |||
| 63bc54fd1b | |||
| d7d2505847 | |||
| 93e568624e | |||
| 335309e20a | |||
| 121457d793 | |||
| 6f3cb577b0 | |||
| 9386668db0 | |||
| be2235b873 | |||
| 8c351440c8 | |||
| 5a745659b9 | |||
| ab5a74eac7 | |||
| 268cea0550 | |||
| 5b7bc95614 | |||
| 41b6af88db | |||
| c75a8a4274 | |||
| 4a6bdfb215 | |||
| f85e86bedf | |||
| 2e858e8506 | |||
| 1c104af4a6 | |||
| fafe20e9e2 | |||
| 83c19a7799 | |||
| a990903e3d | |||
| a9a518e508 | |||
| 934aa9bc89 | |||
| 6406580385 | |||
| 562c854a5b | |||
| 73994d3a4e | |||
| ac2f974e5a | |||
| cd25d23246 | |||
| 8d2f3ef88e | |||
| 8e53d3b306 | |||
| bd95c3d0c4 | |||
| 421a350f57 | |||
| dceb84669b | |||
| eaeb625d51 | |||
| e980dbf884 | |||
| dfa991b90a | |||
| 3b40250488 | |||
| 8e8992f534 | |||
| 076efda195 | |||
| 5c1e802a6f | |||
| 750e0f16e1 | |||
| c06083476f | |||
| f784ec6109 |
@@ -41,10 +41,10 @@ jobs:
|
||||
docker push ${{ secrets.REGISTRY_PATH }}/umbrella:${{ gitea.ref_name }}
|
||||
docker push ${{ secrets.REGISTRY_PATH }}/umbrella:$TAG
|
||||
|
||||
- name: Restart vj.srsoftware.de
|
||||
if: github.ref == 'refs/heads/main'
|
||||
- name: Restart umbrella.srsoftware.de
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
curl -X POST -H "Authorization: Bearer ${{ secrets.MAKE_BEARER }}" -d vj_start https://make.srsoftware.de/launch
|
||||
curl -X POST -H "Authorization: Bearer ${{ secrets.ELDORADO_MAKE_BEARER }}" -d umbrella_25_start https://make.eldorado.srsoftware.de/launch
|
||||
|
||||
Clean-Registry:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -22,7 +22,7 @@ RUN apk --no-cache add bash fontconfig font-opensans graphviz openjdk21-jre tzda
|
||||
WORKDIR /home/umbrella
|
||||
|
||||
EXPOSE 80
|
||||
CMD java -jar jar/backend.jar
|
||||
CMD java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9999 -jar jar/backend.jar
|
||||
|
||||
ADD https://github.com/plantuml/plantuml/releases/download/v1.2025.10/plantuml-1.2025.10.jar /home/umbrella/plantuml.jar
|
||||
COPY --from=java_build /Umbrella/backend/build/libs/backend.jar /home/umbrella/jar/
|
||||
|
||||
@@ -42,7 +42,7 @@ public class CompanyModule extends BaseHandler implements CompanyService {
|
||||
var company = get(companyId);
|
||||
if (!membership(companyId,user.id())) throw forbidden("You are mot a member of company {company}", COMPANY,company.name());
|
||||
if (!documentService().list(companyId).isEmpty()) throw forbidden("There are documents owned by {company}", COMPANY,company.name());
|
||||
if (!itemService().redefineMe(companyId).isEmpty()) throw forbidden("There are items owned by {company}", COMPANY,company.name());
|
||||
if (!itemService().getCompanyItems(companyId).isEmpty()) throw forbidden("There are items owned by {company}", COMPANY,company.name());
|
||||
if (!projectService().listCompanyProjects(companyId,true).isEmpty()) throw forbidden("There are projects owned by {company}", COMPANY,company.name());
|
||||
return sendContent(ex, companyDb.drop(companyId));
|
||||
}
|
||||
|
||||
@@ -37,12 +37,51 @@ import org.json.JSONObject;
|
||||
public class Util {
|
||||
public static final System.Logger LOG = System.getLogger("Util");
|
||||
private static final Pattern UML_PATTERN = Pattern.compile("@start(\\w+)(.*?)@end(\\1)",Pattern.DOTALL);
|
||||
private static final Pattern SPREADSHEET_PATTERN = Pattern.compile("@startsheet(.*?)@endsheet",Pattern.DOTALL);
|
||||
private static File plantumlJar = null;
|
||||
private static final JParsedown MARKDOWN = new JParsedown();
|
||||
public static final String SHA1 = "SHA-1";
|
||||
private static final MessageDigest SHA1_DIGEST;
|
||||
private static final Map<Integer,String> umlCache = new HashMap<>();
|
||||
|
||||
private static final String SCRIPT = """
|
||||
<script src="http://127.0.0.1:8080/js/jspreadsheet-ce.js"></script>
|
||||
<div id="spreadsheet"></div>
|
||||
<script type="application/javascript">
|
||||
alert('Test');
|
||||
jspreadsheet(document.getElementById('spreadsheet'), {
|
||||
worksheets: [
|
||||
{
|
||||
data: [
|
||||
['Jazz', 'Honda', '2019-02-12', '', true, '$ 2.000,00', '#777700'],
|
||||
['Civic', 'Honda', '2018-07-11', '', true, '$ 4.000,01', '#007777'],
|
||||
],
|
||||
columns: [
|
||||
{ type: 'text', title: 'Car', width: 120 },
|
||||
{
|
||||
type: 'dropdown',
|
||||
title: 'Make',
|
||||
width: 200,
|
||||
source: ['Alfa Romeo', 'Audi', 'Bmw', 'Honda'],
|
||||
},
|
||||
{ type: 'calendar', title: 'Available', width: 200 },
|
||||
{ type: 'image', title: 'Photo', width: 120 },
|
||||
{ type: 'checkbox', title: 'Stock', width: 80 },
|
||||
{
|
||||
type: 'numeric',
|
||||
title: 'Price',
|
||||
width: 100,
|
||||
mask: '$ #.##,00',
|
||||
decimal: ',',
|
||||
},
|
||||
{ type: 'color', width: 100, render: 'square' },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
""";
|
||||
|
||||
static {
|
||||
try {
|
||||
SHA1_DIGEST = MessageDigest.getInstance(SHA1);
|
||||
@@ -79,8 +118,22 @@ public class Util {
|
||||
public static String markdown(String source){
|
||||
if (source == null) return source;
|
||||
try {
|
||||
var matcher = SPREADSHEET_PATTERN.matcher(source);
|
||||
var count = 0;
|
||||
while (matcher.find()){
|
||||
count++;
|
||||
var sheetData = matcher.group(0).trim();
|
||||
var start = matcher.start(0);
|
||||
var end = matcher.end(0);
|
||||
source = source.substring(0, start)
|
||||
+ "<div class=\"spreadsheet\" id=\"spreadsheet-"+count+"\">"
|
||||
+ sheetData.substring(11,sheetData.length()-10)
|
||||
+ "</div>"
|
||||
+ source.substring(end);
|
||||
matcher = SPREADSHEET_PATTERN.matcher(source);
|
||||
}
|
||||
if (plantumlJar != null && plantumlJar.exists()) {
|
||||
var matcher = UML_PATTERN.matcher(source);
|
||||
matcher = UML_PATTERN.matcher(source);
|
||||
while (matcher.find()) {
|
||||
var uml = matcher.group(0).trim();
|
||||
var start = matcher.start(0);
|
||||
|
||||
@@ -3,16 +3,12 @@ package de.srsoftware.umbrella.core.api;
|
||||
|
||||
|
||||
import de.srsoftware.umbrella.core.model.DbLocation;
|
||||
import de.srsoftware.umbrella.core.model.Item;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface StockService {
|
||||
/**
|
||||
* Das war mal die methode um zu checken, ob einer Firma noch Items zugewiesen sind.
|
||||
* TODO: Diese Methode muss neu definiert werden, sobald der Stock-Service neu implementiert ist.
|
||||
* @param company_id
|
||||
* @return
|
||||
*/
|
||||
Collection<Object> redefineMe(long company_id);
|
||||
Collection<Item> getCompanyItems(long companyID);
|
||||
|
||||
DbLocation loadLocation(long locationId);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class Project implements Mappable {
|
||||
private final Map<Long,Member> members;
|
||||
private final Collection<Status> allowedStates;
|
||||
private boolean showClosed;
|
||||
private final Long companyId;
|
||||
private Long companyId;
|
||||
private int status;
|
||||
private String name;
|
||||
private final long id;
|
||||
@@ -96,6 +96,7 @@ public class Project implements Mappable {
|
||||
public Project patch(JSONObject json) {
|
||||
for (var key : json.keySet()){
|
||||
switch (key){
|
||||
case COMPANY_ID: companyId = json.getLong(COMPANY_ID); break;
|
||||
case DESCRIPTION: description = json.getString(key); break;
|
||||
case NAME: name = json.getString(key); break;
|
||||
case SHOW_CLOSED: showClosed = json.getBoolean(SHOW_CLOSED); break;
|
||||
|
||||
20
frontend/package-lock.json
generated
20
frontend/package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"jspreadsheet-ce": "^5.0.4",
|
||||
"svelte-tiny-router": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -488,6 +489,11 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jspreadsheet/formula": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@jspreadsheet/formula/-/formula-2.0.2.tgz",
|
||||
"integrity": "sha512-PDQYf9REQA53I7tVYkvkeyQxrd5jcjUeHgItYnRpjN2QiIQwawSqBDtGGEVQTSboTG+JwgGCuhvOpj7FxeKwew=="
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz",
|
||||
@@ -951,6 +957,20 @@
|
||||
"@types/estree": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jspreadsheet-ce": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/jspreadsheet-ce/-/jspreadsheet-ce-5.0.4.tgz",
|
||||
"integrity": "sha512-ra1JI1n+tEGgRMzTzNkPZjG0HZz8W6bFGAiTiHl+eYarXdRmS5qDc/ua3l2ev7oZ6Og9kjfrXYHVLUWiVc308w==",
|
||||
"dependencies": {
|
||||
"@jspreadsheet/formula": "^2.0.2",
|
||||
"jsuites": "^5.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsuites": {
|
||||
"version": "5.13.5",
|
||||
"resolved": "https://registry.npmjs.org/jsuites/-/jsuites-5.13.5.tgz",
|
||||
"integrity": "sha512-cvkcpy/v5I3+IAcNPE4UP38PFCEfUQw9JI5NN61dlcXLwkD+2UTIOsRPvgMLeqI1eDWHL4AHfrbcE/+TFciUsw=="
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "4.1.5",
|
||||
"dev": true,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"vite": "^6.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"jspreadsheet-ce": "^5.0.4",
|
||||
"svelte-tiny-router": "^1.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
import ResetPw from "./routes/user/ResetPw.svelte";
|
||||
import Search from "./routes/search/Search.svelte";
|
||||
import SendDoc from "./routes/document/Send.svelte";
|
||||
import Spreadsheet from "./routes/calc.svelte";
|
||||
import Stock from './routes/stock/Index.svelte';
|
||||
import TagList from "./routes/tags/Index.svelte";
|
||||
import TagUses from "./routes/tags/TagUses.svelte";
|
||||
@@ -89,6 +90,7 @@
|
||||
<Route path="/" component={User} />
|
||||
<Route path="/bookmark" component={Bookmarks} />
|
||||
<Route path="/bookmark/:id/view" component={Bookmark} />
|
||||
<Route path="/calc" component={Spreadsheet} />
|
||||
<Route path="/company" component={Companies} />
|
||||
<Route path="/contact" component={ContactList} />
|
||||
<Route path="/document" component={DocList} />
|
||||
|
||||
82
frontend/src/Components/MarkdownDisplay.svelte
Normal file
82
frontend/src/Components/MarkdownDisplay.svelte
Normal file
@@ -0,0 +1,82 @@
|
||||
<script>
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { t } from '../translations.svelte';
|
||||
|
||||
let { classes='markdown', markdown=$bindable({source:'',rendered:''}), onclick = null, oncontextmenu = null, title='', wrapper = 'div' } = $props();
|
||||
let jspreadsheet = null;
|
||||
const regex = /@startsheet[\s\S]*?@endsheet/g;
|
||||
const number = /^[0-9.-]+$/
|
||||
|
||||
function update(sheet, index){
|
||||
const data = sheet.getData(false,false,'|',false);
|
||||
markdown.source = replaceNthSpreadsheet(markdown.source,index,data);
|
||||
}
|
||||
|
||||
function replaceNthSpreadsheet(text, n, newContent) {
|
||||
const blocks = text.match(regex) || [];
|
||||
if (blocks.length < n+1){
|
||||
console.warn(`cannot replace block ${n}: only ${blocks.length} blocks found!`);
|
||||
return text;
|
||||
}
|
||||
let count = 0;
|
||||
return text.replace(regex, (match) => count++ === n ? `@startsheet\n${newContent}\n@endsheet` : match);
|
||||
}
|
||||
|
||||
function formatCell(cell, value, x, y, instance, options){
|
||||
value = value.trim();
|
||||
if (value.startsWith('=') || number.test(value)) cell.style.textAlign = 'right';
|
||||
}
|
||||
|
||||
async function transform(){
|
||||
if (!markdown.rendered) return;
|
||||
let sheets = document.getElementsByClassName('spreadsheet');
|
||||
for (let i = 0; i < sheets.length; i++) {
|
||||
let sheet = sheets[i];
|
||||
let raw = sheet.innerHTML.trim();
|
||||
if (!jspreadsheet) {
|
||||
sheet.innerHTML = t('Loading spreadsheet library…');
|
||||
let module = await import('jspreadsheet-ce'); // path or package name
|
||||
await import('jspreadsheet-ce/dist/jspreadsheet.css');
|
||||
jspreadsheet = module.default ?? module;
|
||||
}
|
||||
if (!jspreadsheet) break; // break loop if library fails to load
|
||||
sheet.innerHTML = t('Processing spreadsheet data…');
|
||||
|
||||
|
||||
// Use parseCSV from the helpers
|
||||
const parsed = jspreadsheet.helpers.parseCSV(raw, '|');
|
||||
let columns = {};
|
||||
|
||||
for (let row of parsed){
|
||||
for (let col in row){
|
||||
let data = ""+row[col];
|
||||
if (data.startsWith('=')) continue;
|
||||
let len = data.length;
|
||||
columns[col] = Math.max(columns[col]??0,len);
|
||||
}
|
||||
}
|
||||
columns = Object.values(columns).map((len) => {return {
|
||||
align: 'left',
|
||||
render: formatCell,
|
||||
width:`${len}0px`
|
||||
}});
|
||||
let config = {
|
||||
worksheets : [{
|
||||
data:parsed,
|
||||
columns
|
||||
}],
|
||||
onchange : (instance, cell, x, y, value) => update(instance, i)
|
||||
};
|
||||
let wb = jspreadsheet(document.getElementById(sheet.id), config);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => { setTimeout(transform,200)});
|
||||
|
||||
</script>
|
||||
|
||||
{#if markdown.rendered}
|
||||
<svelte:element this={wrapper} class={classes} {onclick} {oncontextmenu} {title}>
|
||||
{@html markdown.rendered}
|
||||
</svelte:element>
|
||||
{/if}
|
||||
@@ -3,6 +3,8 @@
|
||||
import { api, target } from '../urls.svelte.js';
|
||||
import { t } from '../translations.svelte.js';
|
||||
|
||||
import Display from './MarkdownDisplay.svelte';
|
||||
|
||||
let {
|
||||
editable = true,
|
||||
onclick = evt => {},
|
||||
@@ -136,7 +138,7 @@
|
||||
<span id="restore_markdown" onclick={restore} class="hint">{t('unsaved_content')}</span>
|
||||
{/if}
|
||||
<textarea bind:value={editValue.source} onkeyup={typed} autofocus={!simple}></textarea>
|
||||
<div class="preview">{@html target(editValue.rendered)}</div>
|
||||
<Display classes="preview" bind:markdown={editValue} />
|
||||
{#if !simple}
|
||||
<div class="buttons">
|
||||
<button class="cancel" onclick={e => editing = false}>{t('cancel')}</button>
|
||||
@@ -144,6 +146,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<svelte:element this={type} {onclick} {oncontextmenu} class={{editable}} title={t('right_click_to_edit')} >{@html target(value.rendered)}</svelte:element>
|
||||
<Display classes={{editable}} markdown={value} {onclick} {oncontextmenu} title={t('right_click_to_edit')} wrapper={type} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
59
frontend/src/routes/calc.svelte
Normal file
59
frontend/src/routes/calc.svelte
Normal file
@@ -0,0 +1,59 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
var spreadsheet = null;
|
||||
|
||||
const config = {
|
||||
worksheets: [{
|
||||
data: [
|
||||
["1","Sum of A:","=SUM(A1:A99)"],
|
||||
["2"],
|
||||
["3"],
|
||||
["4"]
|
||||
],
|
||||
columns: [
|
||||
{ type: 'autonumber', title: 'amount' },
|
||||
{ type: 'text', width: '350px', title: 'description', align: 'right' },
|
||||
{ type: 'text', width: '250px', title: 'value' },
|
||||
],
|
||||
// Name of the worksheet
|
||||
worksheetName: 'Albums'
|
||||
}],
|
||||
onchange: update
|
||||
};
|
||||
|
||||
|
||||
function update(instance, cell, x, y, value) {
|
||||
console.log({instance,cell,x,y,value});
|
||||
console.log(spreadsheet[0].getData());
|
||||
}
|
||||
|
||||
let loading = true;
|
||||
let module = null;
|
||||
|
||||
async function load(){
|
||||
try {
|
||||
const module = await import('jspreadsheet-ce'); // path or package name
|
||||
await import('jspreadsheet-ce/dist/jspreadsheet.css');
|
||||
|
||||
let jspreadsheet = module.default ?? module;
|
||||
let element = document.getElementById('spreadsheet');
|
||||
console.log(element);
|
||||
spreadsheet = jspreadsheet(element, config);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
Loading…
|
||||
{:else}
|
||||
|
||||
{/if}
|
||||
<div id="spreadsheet">Spreadsheet loading…</div>
|
||||
@@ -2,11 +2,12 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
|
||||
import { api } from '../../urls.svelte.js';
|
||||
import { api, post } from '../../urls.svelte.js';
|
||||
import { error, yikes } from '../../warn.svelte';
|
||||
import { t } from '../../translations.svelte.js';
|
||||
|
||||
import Position from './Position.svelte';
|
||||
import PositionSelector from './PositionSelector.svelte';
|
||||
|
||||
var {
|
||||
document = $bindable(null),
|
||||
@@ -15,6 +16,18 @@
|
||||
|
||||
let editable = $derived(document.state == 1);
|
||||
let sums = $derived.by(calcSums);
|
||||
let position_select = $state(false);
|
||||
|
||||
async function addPosition(selected){
|
||||
const url = api(`document/${document.id}/position`);
|
||||
const resp = await post(url,selected);
|
||||
if (resp.ok){
|
||||
document.positions = await resp.json();
|
||||
yikes();
|
||||
} else {
|
||||
error(resp);
|
||||
}
|
||||
}
|
||||
|
||||
function calcSums(){
|
||||
let data = {}
|
||||
@@ -69,6 +82,12 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<legend>
|
||||
{t('positions')}
|
||||
{#if editable}
|
||||
<button onclick={() => position_select = true}>{t('add_object',{object:t('position')})}</button>
|
||||
{/if}
|
||||
</legend>
|
||||
{#if document.positions}
|
||||
<table class="positions">
|
||||
<thead>
|
||||
@@ -98,3 +117,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
{#if position_select}
|
||||
<PositionSelector close={() => position_select=false} doc={document} onSelect={addPosition} />
|
||||
{/if}
|
||||
@@ -13,13 +13,8 @@
|
||||
|
||||
let source = $state(0);
|
||||
|
||||
function select(position){
|
||||
close();
|
||||
onSelect(position);
|
||||
}
|
||||
|
||||
function estimateSelected(estimate){
|
||||
select({
|
||||
onSelect({
|
||||
item_code : t('estimated_time'),
|
||||
title : estimate.name,
|
||||
description : estimate.description.source,
|
||||
@@ -56,11 +51,11 @@
|
||||
};
|
||||
if (tax) data['tax'] = tax;
|
||||
|
||||
select(data);
|
||||
onSelect(data);
|
||||
}
|
||||
|
||||
function timeSelected(time){
|
||||
select({
|
||||
onSelect({
|
||||
item_code : t('timetrack'),
|
||||
title : time.subject,
|
||||
description : time.description.source,
|
||||
@@ -100,6 +95,7 @@
|
||||
{:else if source == 1}
|
||||
<EstimateList company_id={doc.company.id} onSelect={estimateSelected} />
|
||||
{:else}
|
||||
<TimeList company_id={doc.company.id} onSelect={timeSelected} />
|
||||
<TimeList company_id={doc.company.id} hide={doc.positions} onSelect={timeSelected} />
|
||||
{/if}
|
||||
</div>
|
||||
<pre>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { api, target } from '../../urls.svelte.js';
|
||||
import { api, post, target } from '../../urls.svelte.js';
|
||||
import { error, yikes } from '../../warn.svelte';
|
||||
import { t } from '../../translations.svelte.js';
|
||||
|
||||
let {
|
||||
company_id,
|
||||
hide = {},
|
||||
onSelect = (time) => {}
|
||||
} = $props();
|
||||
|
||||
@@ -16,11 +17,7 @@
|
||||
async function loadProjects(){
|
||||
const url = api('project/list');
|
||||
let data = { company_id: company_id };
|
||||
const resp = await fetch(url,{
|
||||
credentials : 'include',
|
||||
method : 'POST',
|
||||
body : JSON.stringify(data)
|
||||
});
|
||||
const resp = await post(url,data);
|
||||
if (resp.ok){
|
||||
projects = await resp.json();
|
||||
yikes();
|
||||
@@ -29,19 +26,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
function filter(newTimes, excluded_ids){
|
||||
times = newTimes.filter(time => !excluded_ids.includes(time.id));
|
||||
}
|
||||
|
||||
async function loadTimes(projectId){
|
||||
const url = api('time/list');
|
||||
let data = { company_id: company_id, project_id: projectId };
|
||||
const resp = await fetch(url,{
|
||||
credentials : 'include',
|
||||
method : 'POST',
|
||||
body : JSON.stringify(data)
|
||||
});
|
||||
const resp = await post(url,data);
|
||||
if (resp.ok){
|
||||
times = await resp.json();
|
||||
} else {
|
||||
error(resp);
|
||||
}
|
||||
yikes();
|
||||
let newTimes = await resp.json();
|
||||
let present_times = Object.values(hide).filter(pos => pos.time_id).map(pos => pos.time_id)
|
||||
filter(newTimes,present_times);
|
||||
} else error(resp);
|
||||
}
|
||||
|
||||
function select(time){
|
||||
filter(times,[time.id]);
|
||||
onSelect(time);
|
||||
}
|
||||
|
||||
onMount(loadProjects);
|
||||
@@ -56,12 +59,13 @@
|
||||
{/if}
|
||||
{#if times}
|
||||
{#each times as time,idx2}
|
||||
<div class="time" onclick={() => onSelect(time)}>
|
||||
<div class="time" onclick={() => select(time)}>
|
||||
<span class="duration">{(time.duration).toFixed(3)} {t('hours')}</span>
|
||||
<span class="subject">{time.subject}</span>
|
||||
<span class="start_time">{time.start_time}</span>
|
||||
<span class="start_time">{new Date(time.start_time*1000).toUTCString()}</span>
|
||||
<span class="description">{@html target(time.description.rendered)}</span>
|
||||
</div>
|
||||
<hr/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -12,13 +12,11 @@
|
||||
import MultilineEditor from '../../Components/MultilineEditor.svelte';
|
||||
import Notes from '../notes/RelatedNotes.svelte';
|
||||
import PositionList from './PositionList.svelte';
|
||||
import PositionSelector from './PositionSelector.svelte';
|
||||
import StateSelector from './StateSelector.svelte';
|
||||
import Tags from '../tags/TagList.svelte';
|
||||
import TemplateSelector from './TemplateSelector.svelte';
|
||||
import TypeSelector from './TypeSelector.svelte';
|
||||
|
||||
|
||||
let doc = $state(null);
|
||||
let editable = $derived(doc.state == 1);
|
||||
let { id } = $props();
|
||||
@@ -27,16 +25,7 @@
|
||||
const router = useTinyRouter();
|
||||
let sndDisabled = $state(false);
|
||||
|
||||
async function addPosition(selected){
|
||||
const url = api(`document/${doc.id}/position`);
|
||||
const resp = await post(url,selected);
|
||||
if (resp.ok){
|
||||
doc.positions = await resp.json();
|
||||
yikes();
|
||||
} else {
|
||||
error(resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function changeState(newVal){
|
||||
let success = false;
|
||||
@@ -234,12 +223,7 @@
|
||||
<MarkdownEditor bind:value={doc.head} editable={editable} onSet={(val) => update('head',val)} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>
|
||||
{t('positions')}
|
||||
{#if editable}
|
||||
<button onclick={() => position_select = true}>{t('add_object',{object:t('position')})}</button>
|
||||
{/if}
|
||||
</legend>
|
||||
|
||||
<PositionList bind:document={doc} submit={update} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@@ -262,6 +246,3 @@
|
||||
<Notes module="document" entity_id={id} />
|
||||
</div>
|
||||
|
||||
{#if position_select}
|
||||
<PositionSelector close={() => position_select=false} {doc} onSelect={addPosition} />
|
||||
{/if}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { error, yikes } from '../../warn.svelte';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
import CompanySelector from '../../Components/CompanySelector.svelte';
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
|
||||
import PermissionEditor from '../../Components/PermissionEditor.svelte';
|
||||
@@ -211,6 +212,11 @@
|
||||
{#if project.company}
|
||||
<div>{t('company')}</div>
|
||||
<div class="company">{project.company.name}</div>
|
||||
{:else}
|
||||
{#if showSettings}
|
||||
<div>{t('company')}</div>
|
||||
<span><CompanySelector caption={t('select_company')} onselect={c => update({company_id:c.id})} /></span>
|
||||
{/if}
|
||||
{/if}
|
||||
<div>{t('context')}</div>
|
||||
<div>
|
||||
|
||||
@@ -76,11 +76,9 @@
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t('ID')}</td>
|
||||
<td>{item.id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t('Code')}:</td>
|
||||
<td>
|
||||
{t('ID')}
|
||||
</td>
|
||||
<td>
|
||||
<LineEditor type="span" editable={true} value={item.code} onSet={v => update('code',v)} />
|
||||
</td>
|
||||
|
||||
@@ -88,8 +88,8 @@
|
||||
function onclick(evt) {
|
||||
ignore(evt);
|
||||
let task = getTask(evt);
|
||||
if (task.status <= 20) { // open
|
||||
update(task,60);
|
||||
if (task.status == 20) { // open
|
||||
update(task,10);
|
||||
} else update(task,20);
|
||||
return false;
|
||||
}
|
||||
@@ -153,7 +153,7 @@
|
||||
<legend>{t('state_complete')}</legend>
|
||||
{#if sorted}
|
||||
{#each sorted as task}
|
||||
{#if task.status > 20 && match(task)}
|
||||
{#if task.status < 20 && match(task)}
|
||||
<div href={`/task/${task.id}/view`} title={task.description.source} task_id={task.id} {onclick} {oncontextmenu} {ontouchstart} {ontouchend} onmousedown={ontouchstart} onmouseup={ontouchend} >
|
||||
{task.name}
|
||||
</div>
|
||||
|
||||
84
legacy/src/main/resources/css/default/colors.css
Normal file
84
legacy/src/main/resources/css/default/colors.css
Normal file
@@ -0,0 +1,84 @@
|
||||
body{
|
||||
color: orange;
|
||||
background-color: black;
|
||||
}
|
||||
a{
|
||||
color: red;
|
||||
}
|
||||
|
||||
.tasks .pending>a{
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.completed>a,
|
||||
.started>a{
|
||||
color: #5bc500;
|
||||
}
|
||||
|
||||
.canceled>a{
|
||||
color: gray;
|
||||
}
|
||||
|
||||
tr{
|
||||
background-color: #52525270;
|
||||
}
|
||||
tr:nth-child(2n+1){
|
||||
background-color: #a7a7a740;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border-color: orange;
|
||||
}
|
||||
|
||||
.button,
|
||||
input,
|
||||
button {
|
||||
background-color: orange;
|
||||
color: #303030;
|
||||
}
|
||||
.hover{
|
||||
background-color: black;
|
||||
border-color: orange;
|
||||
}
|
||||
|
||||
form.invoice textarea {
|
||||
background-color: orange;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.infos span{
|
||||
background-color: #00ad00;
|
||||
color: black;
|
||||
}
|
||||
.errors span{
|
||||
background-color: #ff9847;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.warnings span{
|
||||
background-color: #ffff00;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#announce{
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#main_menu .button:hover {
|
||||
color: orange;
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
tr:hover td{
|
||||
background-color: #160202;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: black;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.description img,
|
||||
#preview img{
|
||||
background: lightcyan;
|
||||
}
|
||||
584
legacy/src/main/resources/css/default/style.css
Normal file
584
legacy/src/main/resources/css/default/style.css
Normal file
@@ -0,0 +1,584 @@
|
||||
@font-face {
|
||||
font-family: "awesome";
|
||||
src: url("../fontawesome-webfont.woff");
|
||||
}
|
||||
*{
|
||||
max-width:100%;
|
||||
min-width:auto;
|
||||
}
|
||||
a{
|
||||
text-decoration: none;
|
||||
}
|
||||
body{
|
||||
font-family: sans-serif;
|
||||
}
|
||||
code {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
textarea {
|
||||
min-height: 60px;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
textarea:hover{
|
||||
min-height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type=text],input[type=email]{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td.connectors form input{
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
img#logo{
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
blockquote{
|
||||
font-style: italic;
|
||||
}
|
||||
#main_menu,
|
||||
#main_menu form{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#main_menu .button {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin-top: 7px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.hidden{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hover:hover .hidden{
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
.emphasized{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.add_positions,
|
||||
.requirements{
|
||||
max-height: 60px;
|
||||
max-width: 66%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.add_positions:hover,
|
||||
.requirements:hover{
|
||||
max-height: 999999px;
|
||||
max-width: 999999px;
|
||||
}
|
||||
|
||||
.add_positions > ul > li{
|
||||
max-height: 20px;
|
||||
overflow: hidden;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.add_positions > ul > li:hover{
|
||||
max-height: 999999px;
|
||||
}
|
||||
|
||||
fieldset.add.document:hover {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
fieldset.add.document {
|
||||
max-height: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
fieldset.options label,
|
||||
.add_positions label span,
|
||||
.poll_status label,
|
||||
.requirements label{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tasks .canceled a,
|
||||
.children .inactive,
|
||||
.requirements .inactive,
|
||||
.tasks .inactive{
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.tasks .navi a{
|
||||
display: inline-table;
|
||||
}
|
||||
|
||||
blockquote a,
|
||||
p a,
|
||||
.description a{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
tr > *{
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
label.street:after,
|
||||
label.location::after {
|
||||
content: "\a ";
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.poll.evaluate table tr:nth-child(n+2) th,
|
||||
.poll.evaluate table td{
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.poll .disabled{
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.company div > fieldset,
|
||||
.company > table{
|
||||
margin-right: 10px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
table.time h2{
|
||||
margin-right: 60px;
|
||||
}
|
||||
|
||||
.file label,
|
||||
.document .dates label{
|
||||
display: block
|
||||
}
|
||||
.document .header,
|
||||
.document .tags{
|
||||
clear: both;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border-radius: 10px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.contacts fieldset,
|
||||
.document .customer,
|
||||
.document .document_type,
|
||||
.document .sender,
|
||||
.document .court,
|
||||
.document .dates,
|
||||
.document .template,
|
||||
fieldset.del_note,
|
||||
fieldset.login_service_list,
|
||||
fieldset.userlist,
|
||||
fieldset.document.list{
|
||||
float: left;
|
||||
}
|
||||
|
||||
.document .dates label{
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.button,
|
||||
input,
|
||||
button {
|
||||
border: 0 none;
|
||||
font-weight: bold;
|
||||
margin: 0 2px;
|
||||
padding: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.change_state,
|
||||
td.connectors button[type="submit"],
|
||||
.prop_action,
|
||||
input[type="submit"] {
|
||||
clear: both;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.symbol{
|
||||
font-family: awesome;
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#main_menu .symbol{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.right{
|
||||
float: right;
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.tasks .project {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.right-abs {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
.right-fix {
|
||||
position: fixed;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
.hover {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-radius: 5px;
|
||||
max-height: 35px;
|
||||
overflow: hidden;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
.hover:hover {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
.hover_h > a:nth-child(n+2){
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hover_h:hover {
|
||||
padding: 5px 0 20px;
|
||||
}
|
||||
|
||||
.hover_h:hover > a:nth-child(n+2){
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
form.document textarea {
|
||||
font-weight: bold;
|
||||
min-height: 90px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pos_price,
|
||||
form.document input.price,
|
||||
form.document input.amount{
|
||||
max-width: 60px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
form.document .tax{
|
||||
min-width: 70px;
|
||||
}
|
||||
form.document .tax input{
|
||||
max-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td > h1{
|
||||
display: inline-block;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.center,
|
||||
.infos,
|
||||
.errors,
|
||||
.warnings{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.infos span,
|
||||
.errors span,
|
||||
.warnings span{
|
||||
margin: 5px 0 0;
|
||||
padding: 5px;
|
||||
border-radius: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tags span{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.bookmark .share,
|
||||
.bookmark .tags{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bookmark form .share{
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bookmark form .share:hover{
|
||||
height: initial;
|
||||
}
|
||||
|
||||
fieldset.bookmark > fieldset{
|
||||
max-height: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
fieldset.bookmark > fieldset.tags{
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
fieldset.bookmark > fieldset:hover{
|
||||
max-height: unset;
|
||||
}
|
||||
.bookmark>fieldset>a{
|
||||
word-wrap: break-word;
|
||||
display: block;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.bookmark>fieldset>a.button{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.copytext{
|
||||
position:fixed;
|
||||
width: 100px;
|
||||
left: -1000px;
|
||||
}
|
||||
|
||||
img[src*="pos=right"] {
|
||||
float:right;
|
||||
margin: 5px 0 5px 5px;
|
||||
}
|
||||
|
||||
img[src*="pos=left"] {
|
||||
float:left;
|
||||
margin: 5px 5px 5px 0;
|
||||
}
|
||||
|
||||
img[src*="width=100"]{
|
||||
max-width: 100px;
|
||||
}
|
||||
img[src*="width=200"]{
|
||||
max-width: 200px;
|
||||
}
|
||||
img[src*="width=300"]{
|
||||
max-width: 300px;
|
||||
}
|
||||
img[src*="width=400"]{
|
||||
max-width: 400px;
|
||||
}
|
||||
img[src*="width=500"]{
|
||||
max-width: 500px;
|
||||
}
|
||||
img[src*="width=600"]{
|
||||
max-width: 600px;
|
||||
}
|
||||
img[src*="width=700"]{
|
||||
max-width: 700px;
|
||||
}
|
||||
img[src*="width=800"]{
|
||||
max-width: 800px;
|
||||
}
|
||||
img[src*="width=900"]{
|
||||
max-width: 900px;
|
||||
}
|
||||
img[src*="width=50%"]{
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
img[src*="width=33%"]{
|
||||
max-width: 33%;
|
||||
}
|
||||
|
||||
img[src$="width=25%"]{
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
#announce{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 3px 5px 0;
|
||||
border-top-left-radius: 7px;
|
||||
}
|
||||
|
||||
fieldset.scrolling{
|
||||
overflow: scroll;
|
||||
max-height: 80%;
|
||||
}
|
||||
|
||||
svg#gantt{
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
svg .row{
|
||||
fill: none;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
svg .row:hover{
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
svg .duration{
|
||||
fill:rgba(255,0,255,0.4);
|
||||
stroke:none;
|
||||
}
|
||||
|
||||
svg .schedule{
|
||||
fill:none;
|
||||
stroke-width:1;
|
||||
stroke: yellow;
|
||||
}
|
||||
|
||||
svg .start{
|
||||
stroke-dasharray: 10,30,40;
|
||||
}
|
||||
|
||||
svg .stop{
|
||||
stroke-dasharray: 50,30;
|
||||
}
|
||||
|
||||
svg text{
|
||||
stroke: none;
|
||||
fill: red;
|
||||
}
|
||||
|
||||
div.search{
|
||||
display: inline-block;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
div.search input,
|
||||
div.search label{
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.search:hover input,
|
||||
div.search:hover label{
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.note td.code {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.note td.code textarea {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.project-index td .users{
|
||||
max-height: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.project-index tr:hover td .users{
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.easylist fieldset a.button {
|
||||
width: 80%;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#preview,
|
||||
#preview-source{
|
||||
display: inline-block;
|
||||
max-width: calc(50% - 20px);
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
table #preview,
|
||||
table #preview-source{
|
||||
display: inherit;
|
||||
max-width: unset;
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
#preview.loading{
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.completed > a::before {
|
||||
content: " ✓";
|
||||
}
|
||||
|
||||
@media (max-width:1199px) {
|
||||
*[hide="12"]{
|
||||
display: none;
|
||||
}
|
||||
table.document input{
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px){
|
||||
*[hide="9"]{
|
||||
display: none;
|
||||
}
|
||||
table.document input{
|
||||
max-width: 60px;
|
||||
}
|
||||
table.document .tax input,
|
||||
table.document input.amount{
|
||||
max-width: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px){
|
||||
*[hide="8"]{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px){
|
||||
*[hide="7"]{
|
||||
display: none;
|
||||
}
|
||||
fieldset.bookmark > fieldset{
|
||||
max-height: 50px;
|
||||
}
|
||||
#preview,
|
||||
#preview-source{
|
||||
display: block;
|
||||
max-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px){
|
||||
*[hide="6"]{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.easylist fieldset a.button {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
@media (max-width: 500px){
|
||||
*[hide="5"]{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: 400px){
|
||||
*[hide="4"]{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media print{
|
||||
fieldset.note span.right,
|
||||
#logo, #main_menu {
|
||||
display: none;
|
||||
}
|
||||
fieldset.process, fieldset.database{
|
||||
page-break-after: always;
|
||||
}
|
||||
.export>fieldset{
|
||||
border: 0 none;
|
||||
}
|
||||
}
|
||||
41
legacy/src/main/resources/css/default/svg_colors.css
Normal file
41
legacy/src/main/resources/css/default/svg_colors.css
Normal file
@@ -0,0 +1,41 @@
|
||||
.arrow{
|
||||
stroke: orange;
|
||||
}
|
||||
circle, ellipse, rect{
|
||||
stroke: black;
|
||||
}
|
||||
ellipse,
|
||||
circle.process{
|
||||
fill: rgba(255,255,255,0.5);
|
||||
}
|
||||
rect.terminal{
|
||||
fill: white;
|
||||
}
|
||||
circle.connector{
|
||||
fill: rgba(0,0,0,0.05);
|
||||
stroke: none;
|
||||
}
|
||||
circle.connector:hover{
|
||||
fill: rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
#backdrop{
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
.arrow circle{
|
||||
stroke: none;
|
||||
fill: rgba(128,128,128,0.5);
|
||||
}
|
||||
.arrow text{
|
||||
fill: black;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.arrow:hover circle{
|
||||
fill: rgba(255,0,0,0.3);
|
||||
}
|
||||
|
||||
.process text{
|
||||
fill: black;
|
||||
}
|
||||
@@ -12,9 +12,18 @@ public class Constants {
|
||||
public static final String CONFIG_SMTP_USER = "umbrella.modules.message.smtp.user";
|
||||
|
||||
public static final String DEBUG_ADDREESS = "umbrella.modules.message.debug_address";
|
||||
|
||||
public static final String ENVELOPE_FROM = "mail.smtp.from";
|
||||
public static final String PORT = "mail.smtp.port";
|
||||
|
||||
public static final String SMTP_AUTH = "mail.smtp.auth";
|
||||
public static final String SMTP_HOST = "mail.smtp.host";
|
||||
public static final String SMTP_FROM = "mail.smtp.from";
|
||||
public static final String SMTP_PORT = "mail.smtp.port";
|
||||
public static final String SMTP_SSL = "mail.smtp.ssl.enable";
|
||||
public static final String SSL = "mail.smtp.ssl.enable";
|
||||
public static final String SUBMISSION = "submission";
|
||||
|
||||
public static final String PORT = "mail.smtp.port";
|
||||
|
||||
public static final String TABLE_ATTACHMENTS = "attachments";
|
||||
public static final String TABLE_MESSAGES = "messages";
|
||||
|
||||
@@ -93,10 +93,10 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener
|
||||
|
||||
debugAddress = config.get(DEBUG_ADDREESS).map(Object::toString).orElse(null);
|
||||
port = config.get(CONFIG_SMTP_PORT,587);
|
||||
host = config.get(CONFIG_SMTP_HOST).map(Object::toString).orElseThrow(() -> new RuntimeException("umbrella.modules.message.smtp.host not configured!"));
|
||||
user = config.get(CONFIG_SMTP_USER).map(Object::toString).orElseThrow(() -> new RuntimeException("umbrella.modules.message.smtp.user not configured!"));
|
||||
pass = config.get(CONFIG_SMTP_PASS).map(Object::toString).orElseThrow(() -> new RuntimeException("umbrella.modules.message.smtp.pass not configured!"));
|
||||
from = user;
|
||||
host = config.get(CONFIG_SMTP_HOST).map(Object::toString).orElseThrow(() -> missingConfig(CONFIG_SMTP_HOST));
|
||||
user = config.get(CONFIG_SMTP_USER).map(Object::toString).orElseThrow(() -> missingConfig(CONFIG_SMTP_USER));
|
||||
pass = config.get(CONFIG_SMTP_PASS).map(Object::toString).orElseThrow(() -> missingConfig(CONFIG_SMTP_PASS));
|
||||
from = config.get(CONFIG_SMTP_FROM).map(Object::toString).orElseThrow(() -> missingConfig(CONFIG_SMTP_FROM));
|
||||
ModuleRegistry.add(this);
|
||||
new SubmissionTask(8).schedule();
|
||||
new SubmissionTask(10).schedule();
|
||||
@@ -326,11 +326,11 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener
|
||||
private Session session() {
|
||||
if (session == null){
|
||||
Properties props = new Properties();
|
||||
props.put(HOST, host);
|
||||
props.put(PORT, port);
|
||||
props.put(AUTH, true);
|
||||
props.put(SSL, true);
|
||||
props.put(ENVELOPE_FROM,from);
|
||||
props.put(SMTP_HOST, host);
|
||||
props.put(SMTP_PORT, port);
|
||||
props.put(SMTP_AUTH, true);
|
||||
props.put(SMTP_SSL, true);
|
||||
props.put(SMTP_FROM,from);
|
||||
session = Session.getInstance(props);
|
||||
}
|
||||
return session;
|
||||
|
||||
@@ -27,9 +27,9 @@ public class CombinedMessage {
|
||||
public CombinedMessage(Translatable subjectForCombinedMessage, User receiver, User fallbackSender){
|
||||
LOG.log(DEBUG,"Creating combined message for {0}…",receiver);
|
||||
this.subjectForCombinedMessage = subjectForCombinedMessage;
|
||||
this.fallbackSender = fallbackSender;
|
||||
this.receiver = receiver;
|
||||
this.lang = receiver.language();
|
||||
this.fallbackSender = fallbackSender;
|
||||
}
|
||||
|
||||
public void addNote() {
|
||||
|
||||
@@ -9,8 +9,7 @@ import static de.srsoftware.umbrella.core.constants.Field.PERMISSION;
|
||||
import static de.srsoftware.umbrella.core.constants.Field.SETTINGS;
|
||||
import static de.srsoftware.umbrella.core.constants.Field.TAGS;
|
||||
import static de.srsoftware.umbrella.core.constants.Module.PROJECT;
|
||||
import static de.srsoftware.umbrella.core.constants.Path.LIST;
|
||||
import static de.srsoftware.umbrella.core.constants.Path.SEARCH;
|
||||
import static de.srsoftware.umbrella.core.constants.Path.*;
|
||||
import static de.srsoftware.umbrella.core.constants.Text.*;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
||||
import static de.srsoftware.umbrella.core.model.Permission.*;
|
||||
|
||||
@@ -251,7 +251,7 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
}
|
||||
if (prj.isDirty()){
|
||||
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().orElse(null),prj.showClosed())
|
||||
.execute();
|
||||
prj.clean();
|
||||
}
|
||||
|
||||
@@ -226,7 +226,8 @@ public class StockModule extends BaseHandler implements StockService {
|
||||
}
|
||||
|
||||
private boolean getChildLocations(UmbrellaUser user, long parentId, HttpExchange ex) throws IOException {
|
||||
LOG.log(WARNING,"No security check implemented for {0}.getChildLocations(user, parentId, ex)!",getClass().getSimpleName()); // TODO check, that user is allowed to request that location
|
||||
var owner = stockDb.loadLocation(parentId).owner();
|
||||
if (!assigned(owner,user)) throw forbidden("You are not allowed to access items of {owner}", OWNER,owner);
|
||||
return sendContent(ex, stockDb.listChildLocations(parentId).stream().sorted(comparing(l -> l.name().toLowerCase())).map(DbLocation::toMap));
|
||||
}
|
||||
|
||||
@@ -288,17 +289,19 @@ public class StockModule extends BaseHandler implements StockService {
|
||||
Field.LOCATIONS,userLocations.stream().map(DbLocation::toMap).toList()));
|
||||
|
||||
var companies = companyService().listCompaniesOf(user);
|
||||
companies.values().stream().sorted(comparing(a -> a.name().toLowerCase())).forEach(company -> {
|
||||
var locations = stockDb.listCompanyLocations(company);
|
||||
result.add(Map.of(
|
||||
PARENT, Map.of(Field.COMPANY, company.id()),
|
||||
NAME,company.name(),
|
||||
Field.LOCATIONS,locations.stream().sorted(comparing(a -> a.name().toLowerCase())).map(DbLocation::toMap).toList()));
|
||||
|
||||
});
|
||||
companies.values().stream().sorted(comparing(a -> a.name().toLowerCase()))
|
||||
.map(company -> Map.of(
|
||||
PARENT, Map.of(Field.COMPANY, company.id()),
|
||||
NAME,company.name(),
|
||||
Field.LOCATIONS,getCompanyLocations(company).stream().sorted(comparing(a -> a.name().toLowerCase())).map(DbLocation::toMap).toList()))
|
||||
.forEach(result::add);
|
||||
return sendContent(ex, result);
|
||||
}
|
||||
|
||||
public Collection<DbLocation> getCompanyLocations(Company company){
|
||||
return stockDb.listCompanyLocations(company);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbLocation loadLocation(long locationId) {
|
||||
return stockDb.loadLocation(locationId);
|
||||
@@ -465,8 +468,7 @@ public class StockModule extends BaseHandler implements StockService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Object> redefineMe(long company_id) {
|
||||
// TODO
|
||||
return List.of();
|
||||
public Collection<Item> getCompanyItems(long companyID) {
|
||||
return stockDb.listItemsOf(companyService().get(companyID));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,3 +11,20 @@ tasks.processResources {
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
fun download(url : String, destination : String){
|
||||
var destFile = projectDir.toPath().resolve(destination).toFile();
|
||||
destFile.parentFile.mkdirs()
|
||||
if (!destFile.exists()) {
|
||||
System.out.println("Downloading "+url)
|
||||
ant.invokeMethod("get", mapOf("src" to url, "dest" to destFile))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("downloadLib"){
|
||||
download("https://bossanova.uk/jspreadsheet/v5/jspreadsheet.js", "src/main/resources/web/js/jspreadsheet-ce.js")
|
||||
}
|
||||
|
||||
tasks.named("compileJava") {
|
||||
dependsOn("downloadLib")
|
||||
}
|
||||
|
||||
@@ -317,6 +317,23 @@ tr:hover .taglist .tag button {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
.jss_worksheet{
|
||||
background: black !important;
|
||||
border-right: 1px solid #333 !important;
|
||||
border-bottom: 1px solid #333 !important;
|
||||
}
|
||||
|
||||
.jss_worksheet > thead > tr > td,
|
||||
.jss_worksheet > tbody > tr > td:first-child{
|
||||
background: #730000 !important;
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.jss_worksheet td{
|
||||
border-top: 1px solid #333 !important;
|
||||
border-left: 1px solid #333 !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
#app nav a{
|
||||
background: black;
|
||||
|
||||
@@ -308,6 +308,23 @@ tr:hover .taglist .tag button {
|
||||
background: #a00;
|
||||
}
|
||||
|
||||
.jss_worksheet{
|
||||
background: black !important;
|
||||
border-right: 1px solid #333 !important;
|
||||
border-bottom: 1px solid #333 !important;
|
||||
}
|
||||
|
||||
.jss_worksheet > thead > tr > td,
|
||||
.jss_worksheet > tbody > tr > td:first-child{
|
||||
background: orange !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.jss_worksheet td{
|
||||
border-top: 1px solid #333 !important;
|
||||
border-left: 1px solid #333 !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
#app nav a{
|
||||
background: black;
|
||||
|
||||
8
web/src/main/resources/web/js/jspreadsheet-ce.js
Normal file
8
web/src/main/resources/web/js/jspreadsheet-ce.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user