mountain-ui 1.0.110 → 1.0.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/index.d.ts +29 -0
  2. package/index.js +29 -0
  3. package/package.json +1 -1
  4. package/src/lib/registry/navigationRegistry/components/NavigationGroupRow.svelte +30 -32
  5. package/src/lib/registry/navigationRegistry/components/NewNavbarGroupDialog.svelte +43 -39
  6. package/src/lib/registry/projectRegistry/createProject.js +2 -0
  7. package/src/lib/registry/widgetGroupRegistry/components/EmptySlot.svelte +47 -0
  8. package/src/lib/registry/widgetGroupRegistry/components/GroupPreview.svelte +142 -0
  9. package/src/lib/registry/widgetGroupRegistry/components/NewWidgetGroup.svelte +122 -0
  10. package/src/lib/registry/widgetGroupRegistry/components/WidgetGroup.svelte +39 -0
  11. package/src/lib/registry/widgetGroupRegistry/createWidget.js +17 -0
  12. package/src/lib/registry/widgetGroupRegistry/createWidgetGroup.js +19 -0
  13. package/src/lib/registry/widgetGroupRegistry/registerWidgetGroups.js +37 -0
  14. package/src/lib/registry/widgetGroupRegistry/registry.svelte.js +47 -0
  15. package/src/lib/registry/widgetTypeRegistry/components/CreateWidgetDialog.svelte +25 -0
  16. package/src/lib/registry/widgetTypeRegistry/components/Widget.svelte +18 -0
  17. package/src/lib/registry/widgetTypeRegistry/components/WidgetConfig.svelte +16 -0
  18. package/src/lib/registry/widgetTypeRegistry/components/WidgetPreview.svelte +59 -0
  19. package/src/lib/registry/widgetTypeRegistry/components/WidgetTypeButton.svelte +20 -0
  20. package/src/lib/registry/widgetTypeRegistry/components/WidgetTypeSelector.svelte +32 -0
  21. package/src/lib/registry/widgetTypeRegistry/createWidgetType.js +20 -0
  22. package/src/lib/registry/widgetTypeRegistry/registerWidgetTypes.js +14 -0
  23. package/src/lib/registry/widgetTypeRegistry/registry.svelte.js +43 -0
  24. package/src/lib/registry/widgetTypeRegistry/widgetTypes/Bar/Bar.svelte +79 -0
  25. package/src/lib/registry/widgetTypeRegistry/widgetTypes/Bar/ConfigBar.svelte +176 -0
  26. package/src/lib/registry/widgetTypeRegistry/widgetTypes/Bar/index.js +13 -0
  27. package/src/lib/registry/widgetTypeRegistry/widgetTypes/Bar/text.js +21 -0
  28. package/src/lib/registry/widgetTypeRegistry/widgetTypes/Evolution/ConfigEvolution.svelte +180 -0
  29. package/src/lib/registry/widgetTypeRegistry/widgetTypes/Evolution/Evolution.svelte +81 -0
  30. package/src/lib/registry/widgetTypeRegistry/widgetTypes/Evolution/index.js +13 -0
  31. package/src/lib/registry/widgetTypeRegistry/widgetTypes/Evolution/text.js +21 -0
  32. package/src/lib/registry/widgetTypeRegistry/widgetTypes/KPI/ConfigKPI.svelte +201 -0
  33. package/src/lib/registry/widgetTypeRegistry/widgetTypes/KPI/KPI.svelte +118 -0
  34. package/src/lib/registry/widgetTypeRegistry/widgetTypes/KPI/index.js +12 -0
  35. package/src/lib/registry/widgetTypeRegistry/widgetTypes/KPI/text.js +22 -0
  36. package/src/lib/registry/widgetTypeRegistry/widgetTypes/List/ConfigList.svelte +199 -0
  37. package/src/lib/registry/widgetTypeRegistry/widgetTypes/List/List.svelte +97 -0
  38. package/src/lib/registry/widgetTypeRegistry/widgetTypes/List/index.js +12 -0
  39. package/src/lib/registry/widgetTypeRegistry/widgetTypes/List/text.js +21 -0
  40. package/src/routes/exemple/[project_normalized_name]/+layout.svelte +1 -1
  41. package/src/routes/exemple/[project_normalized_name]/home/+page.svelte +61 -1
  42. package/src/routes/exemple/[project_normalized_name]/navigation/+page.svelte +28 -0
  43. package/src/routes/exemple/[project_normalized_name]/widgets/+page.svelte +92 -0
@@ -0,0 +1,21 @@
1
+
2
+
3
+ export const frequencyText = {
4
+ 'day': 'aujourd\'hui',
5
+ 'month': 'ce mois-ci',
6
+ 'year': 'cette année'
7
+ }
8
+
9
+ export const frequencyTextForConfig = {
10
+ 'day': 'par jour',
11
+ 'month': 'par mois',
12
+ 'year': 'par an'
13
+ }
14
+
15
+ export const operationText = {
16
+ 'count': 'nombre d\'entrée',
17
+ 'sum': 'somme',
18
+ 'avg': 'moyenne',
19
+ 'min': "min",
20
+ 'max': "max"
21
+ }
@@ -0,0 +1,180 @@
1
+ <script>
2
+ import { Button, DropdownMenu, Input, sendRequest } from 'hamzus-ui';
3
+ import { frequencyText, frequencyTextForConfig } from './text';
4
+ import { entityRegistry } from '$lib/registry/entityRegistry/registry.svelte';
5
+
6
+ let {id = null, save = () => {}} = $props()
7
+
8
+ let title = $state('');
9
+
10
+ const entities = entityRegistry.getAll()
11
+
12
+ let entityId = $state(null);
13
+ const entity = $derived(entityRegistry.get(entityId));
14
+
15
+ let frequency = $state('month'); // day | month | year
16
+ const frequencies = ['day', 'month', 'year'];
17
+
18
+ let valueFieldId = $state(null);
19
+ const valueField = $derived(entityRegistry.getField(valueFieldId));
20
+
21
+ let dateFieldId = $state(null);
22
+ const dateField = $derived(entityRegistry.getField(dateFieldId));
23
+ const dateFieldTypes = ['time-created_at', 'time-datetime', 'time-updated_at'];
24
+
25
+ let operation = 'count'; // count | sum | avg | min | max
26
+ const operations = ['count', 'sum', 'avg', 'min', 'max'];
27
+
28
+ let size = $state('4/2');
29
+ const sizes = ['4/2', '6/2', '8/2', '12/2'];
30
+
31
+ function handleSelectEntity(entity) {
32
+ entityId = entity.id;
33
+
34
+ // parcourir les fields pour set auto le field de value et de date
35
+ for (const field of entity.fields) {
36
+ if (field.type.startsWith('number-') || field.type.startsWith('calculation-')) {
37
+ valueFieldId = field.id;
38
+ }
39
+ if (dateFieldTypes.includes(field.type)) {
40
+ dateFieldId = field.id;
41
+ }
42
+ }
43
+ }
44
+
45
+ async function handleSave() {
46
+ save({
47
+ id,
48
+ title,
49
+ entityId,
50
+ frequency,
51
+ valueFieldId,
52
+ dateFieldId,
53
+ operation,
54
+ size
55
+ });
56
+ }
57
+ </script>
58
+
59
+ <div class="config">
60
+ <Input label="titre" bind:value={title} placeholder="exemple"></Input>
61
+ <h5>Entité</h5>
62
+ <DropdownMenu.Root direction="bottom">
63
+ <DropdownMenu.Trigger slot="trigger">
64
+ <Button variant="secondary">
65
+ <h5>{entity?.plural_name ?? 'choisissez une entité'}</h5>
66
+ </Button>
67
+ </DropdownMenu.Trigger>
68
+ <DropdownMenu.Content slot="content" width="250px">
69
+ {#each $entities as entity}
70
+ <Button
71
+ onClick={() => {
72
+ handleSelectEntity(entity);
73
+ }}
74
+ variant="ghost"
75
+ style="width:100%;"><h5>{entity.plural_name}</h5></Button
76
+ >
77
+ {/each}
78
+ </DropdownMenu.Content>
79
+ </DropdownMenu.Root>
80
+ {#if entity}
81
+ {@const availedValueFields = entity.fields.filter(
82
+ (field) => field.type.startsWith('number-') || field.type.startsWith('calculation-')
83
+ )}
84
+ {@const availedDateFields = entity.fields.filter((field) =>
85
+ dateFieldTypes.includes(field.type)
86
+ )}
87
+ <h5>Champs pour valeurs</h5>
88
+ <DropdownMenu.Root direction="bottom">
89
+ <DropdownMenu.Trigger slot="trigger">
90
+ <Button variant="secondary">
91
+ <h5>{valueField?.name ?? 'choisissez un champ pour valeur'}</h5>
92
+ </Button>
93
+ </DropdownMenu.Trigger>
94
+ <DropdownMenu.Content slot="content" width="250px">
95
+ {#if availedValueFields.length > 0}
96
+ {#each availedValueFields as field}
97
+ <Button
98
+ onClick={() => {
99
+ valueFieldId = field.id;
100
+ }}
101
+ variant="ghost"
102
+ style="width:100%;"><h5>{field.name}</h5></Button
103
+ >
104
+ {/each}
105
+ {:else}
106
+ <div class="nothing">
107
+ <h3>Aucun champs de valeur disponible</h3>
108
+ <p>
109
+ L'entité {entity?.plural_name} ne contient pas de champs nombre, permettant les calculs.
110
+ </p>
111
+ </div>
112
+ {/if}
113
+ </DropdownMenu.Content>
114
+ </DropdownMenu.Root>
115
+ <h5>Champs pour date</h5>
116
+ <DropdownMenu.Root direction="bottom">
117
+ <DropdownMenu.Trigger slot="trigger">
118
+ <Button variant="secondary">
119
+ <h5>{dateField?.name ?? 'choisissez un champ pour date'}</h5>
120
+ </Button>
121
+ </DropdownMenu.Trigger>
122
+ <DropdownMenu.Content slot="content" width="250px">
123
+ {#if availedDateFields.length > 0}
124
+ {#each availedDateFields as field}
125
+ <Button
126
+ onClick={() => {
127
+ dateFieldId = field.id;
128
+ }}
129
+ variant="ghost"
130
+ style="width:100%;"><h5>{field.name}</h5></Button
131
+ >
132
+ {/each}
133
+ {:else}
134
+ <div class="nothing">
135
+ <h3>Aucun champs de date disponible</h3>
136
+ <p>L'entité {entity?.plural_name} ne contient pas de champs de date.</p>
137
+ </div>
138
+ {/if}
139
+ </DropdownMenu.Content>
140
+ </DropdownMenu.Root>
141
+ <h5>fréquence</h5>
142
+ <DropdownMenu.Root direction="bottom">
143
+ <DropdownMenu.Trigger slot="trigger">
144
+ <Button variant="secondary">
145
+ <h5>{frequency ? frequencyTextForConfig[frequency] : 'choisissez une fréquence'}</h5>
146
+ </Button>
147
+ </DropdownMenu.Trigger>
148
+ <DropdownMenu.Content slot="content" width="250px">
149
+ {#each frequencies as frequencyEl}
150
+ <Button
151
+ onClick={() => {
152
+ frequency = frequencyEl;
153
+ }}
154
+ variant="ghost"
155
+ style="width:100%;"><h5>{frequencyTextForConfig[frequencyEl]}</h5></Button
156
+ >
157
+ {/each}
158
+ </DropdownMenu.Content>
159
+ </DropdownMenu.Root>
160
+ {/if}
161
+
162
+ <Button onClick={handleSave}>
163
+ <h5>enregistrer</h5>
164
+ </Button>
165
+ </div>
166
+
167
+ <style>
168
+ .config {
169
+ display: flex;
170
+ flex-direction: column;
171
+ row-gap: var(--pad-m);
172
+ }
173
+
174
+ .nothing {
175
+ display: flex;
176
+ flex-direction: column;
177
+ row-gap: var(--pad-m);
178
+ padding: var(--pad-m);
179
+ }
180
+ </style>
@@ -0,0 +1,81 @@
1
+ <script>
2
+ import { ChartLine, sendRequest, Skeleton } from 'hamzus-ui';
3
+ import { frequencyText, operationText } from './text';
4
+ import { onMount } from 'svelte';
5
+
6
+ let {
7
+ id = '',title = '',entityId = '',frequency = '',valueFieldId = '',dateFieldId = '',operation = '',
8
+ } = $props()
9
+
10
+ let data = $state(null);
11
+ let previousTotal = $state(null);
12
+
13
+ onMount(async () => {
14
+ const request = await sendRequest({
15
+ path: '/get-widget-data',
16
+ data: {
17
+ widgetLinkId: id
18
+ }
19
+ });
20
+
21
+
22
+ data = request.data
23
+ });
24
+
25
+ function getRandomSizes() {
26
+ const sizes = [60, 120, 80];
27
+
28
+ return sizes[Math.floor(Math.random() * sizes.length)];
29
+ }
30
+
31
+ function getPercent() {
32
+ if (previousTotal === 0) {
33
+ if (total === 0) return 0;
34
+ return null; // augmentation non calculable
35
+ }
36
+ return ((total - previousTotal) / previousTotal) * 100;
37
+ }
38
+ </script>
39
+
40
+ <div class="kpi-container">
41
+ <div class="kpi">
42
+ <h4>{title}</h4>
43
+ {#if data === null}
44
+ <Skeleton style="width:{getRandomSizes()}px;height:32px;"></Skeleton>
45
+ {:else}
46
+ <ChartLine {data}
47
+ ></ChartLine>
48
+ {/if}
49
+ </div>
50
+ </div>
51
+
52
+ <style>
53
+ .kpi-container {
54
+ width: 100%;
55
+ height: 100%;
56
+ padding: var(--pad-m);
57
+ }
58
+
59
+ .kpi {
60
+ display: flex;
61
+ flex-direction: column;
62
+ row-gap: var(--pad-m);
63
+ padding: var(--pad-xxl);
64
+ width: 100%;
65
+ height: 100%;
66
+ background-color: var(--bg-blur);
67
+ border-radius: var(--radius-m);
68
+ }
69
+
70
+ .bottom {
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: space-between;
74
+ }
75
+
76
+ .right {
77
+ display: flex;
78
+ align-items: center;
79
+ column-gap: var(--pad-m);
80
+ }
81
+ </style>
@@ -0,0 +1,13 @@
1
+ import ConfigEvolution from "./ConfigEvolution.svelte";
2
+ import Evolution from "./Evolution.svelte";
3
+ import { createWidgetType } from "../../createWidgetType";
4
+
5
+
6
+ export default createWidgetType({
7
+ type: "Evolution",
8
+ name: "Graphique d'évolution",
9
+ components: {
10
+ config: ConfigEvolution,
11
+ component: Evolution,
12
+ }
13
+ })
@@ -0,0 +1,21 @@
1
+
2
+
3
+ export const frequencyText = {
4
+ 'day': 'aujourd\'hui',
5
+ 'month': 'ce mois-ci',
6
+ 'year': 'cette année'
7
+ }
8
+
9
+ export const frequencyTextForConfig = {
10
+ 'day': 'par jour',
11
+ 'month': 'par mois',
12
+ 'year': 'par an'
13
+ }
14
+
15
+ export const operationText = {
16
+ 'count': 'nombre d\'entrée',
17
+ 'sum': 'somme',
18
+ 'avg': 'moyenne',
19
+ 'min': "min",
20
+ 'max': "max"
21
+ }
@@ -0,0 +1,201 @@
1
+ <script>
2
+ import { Button, DropdownMenu, Input, sendRequest } from 'hamzus-ui';
3
+ import { frequencyText, frequencyTextForConfig } from './text';
4
+ import { entityRegistry } from '$lib/registry/entityRegistry/registry.svelte';
5
+
6
+ let { id = null, save = () => {} } = $props();
7
+
8
+ let title = $state('');
9
+
10
+ const entities = entityRegistry.getAll()
11
+
12
+ let entityId = $state(null);
13
+ const entity = $derived(entityRegistry.get(entityId));
14
+
15
+ let frequency = $state('month'); // day | month | year
16
+ const frequencies = ['day', 'month', 'year'];
17
+
18
+ let valueFieldId = $state(null);
19
+ const valueField = $derived(entityRegistry.getField(valueFieldId));
20
+
21
+ let dateFieldId = $state(null);
22
+ const dateField = $derived(entityRegistry.getField(dateFieldId));
23
+ const dateFieldTypes = ['time-created_at', 'time-datetime', 'time-updated_at'];
24
+
25
+ let operation = $state('count'); // count | sum | avg | min | max
26
+ const operations = ['count', 'sum', 'avg', 'min', 'max'];
27
+
28
+ let size = $state('4/1');
29
+ const sizes = ['4/1', '6/1', '8/1', '12/1'];
30
+
31
+ function handleSelectEntity(entity) {
32
+ entityId = entity.id;
33
+
34
+ // parcourir les fields pour set auto le field de value et de date
35
+ for (const field of entity.fields) {
36
+ if (field.type.startsWith('number-') || field.type.startsWith('calculation-')) {
37
+ valueFieldId = field.id;
38
+ }
39
+ if (dateFieldTypes.includes(field.type)) {
40
+ dateFieldId = field.id;
41
+ }
42
+ }
43
+ }
44
+
45
+ async function handleSave() {
46
+ save({
47
+ id,
48
+ title,
49
+ entityId,
50
+ frequency,
51
+ valueFieldId,
52
+ dateFieldId,
53
+ operation,
54
+ size
55
+ });
56
+ }
57
+ </script>
58
+
59
+ <div class="config">
60
+ <Input label="titre" bind:value={title} placeholder="exemple"></Input>
61
+ <h5>opération</h5>
62
+ <DropdownMenu.Root direction="bottom">
63
+ <DropdownMenu.Trigger slot="trigger">
64
+ <Button variant="secondary">
65
+ <h5>{operation ?? 'choisissez une opération'}</h5>
66
+ </Button>
67
+ </DropdownMenu.Trigger>
68
+ <DropdownMenu.Content slot="content" width="250px">
69
+ {#each operations as operationEl}
70
+ <Button
71
+ onClick={() => {
72
+ operation = operationEl;
73
+ }}
74
+ variant="ghost"
75
+ style="width:100%;"><h5>{operationEl}</h5></Button
76
+ >
77
+ {/each}
78
+ </DropdownMenu.Content>
79
+ </DropdownMenu.Root>
80
+ <h5>Entité</h5>
81
+ <DropdownMenu.Root direction="bottom">
82
+ <DropdownMenu.Trigger slot="trigger">
83
+ <Button variant="secondary">
84
+ <h5>{entity?.plural_name ?? 'choisissez une entité'}</h5>
85
+ </Button>
86
+ </DropdownMenu.Trigger>
87
+ <DropdownMenu.Content slot="content" width="250px">
88
+ {#each $entities as entity}
89
+ <Button
90
+ onClick={() => {
91
+ handleSelectEntity(entity);
92
+ }}
93
+ variant="ghost"
94
+ style="width:100%;"><h5>{entity.plural_name}</h5></Button
95
+ >
96
+ {/each}
97
+ </DropdownMenu.Content>
98
+ </DropdownMenu.Root>
99
+ {#if entity}
100
+ {@const availedValueFields = entity.fields.filter(
101
+ (field) => field.type.startsWith('number-') || field.type.startsWith('calculation-')
102
+ )}
103
+ {@const availedDateFields = entity.fields.filter((field) =>
104
+ dateFieldTypes.includes(field.type)
105
+ )}
106
+ {#if operation !== 'count'}
107
+ <h5>Champs pour valeurs</h5>
108
+ <DropdownMenu.Root direction="bottom">
109
+ <DropdownMenu.Trigger slot="trigger">
110
+ <Button variant="secondary">
111
+ <h5>{valueField?.name ?? 'choisissez un champ pour valeur'}</h5>
112
+ </Button>
113
+ </DropdownMenu.Trigger>
114
+ <DropdownMenu.Content slot="content" width="250px">
115
+ {#if availedValueFields.length > 0}
116
+ {#each availedValueFields as field}
117
+ <Button
118
+ onClick={() => {
119
+ valueFieldId = field.id;
120
+ }}
121
+ variant="ghost"
122
+ style="width:100%;"><h5>{field.name}</h5></Button
123
+ >
124
+ {/each}
125
+ {:else}
126
+ <div class="nothing">
127
+ <h3>Aucun champs de valeur disponible</h3>
128
+ <p>
129
+ L'entité {entity?.plural_name} ne contient pas de champs nombre, permettant les calculs.
130
+ </p>
131
+ </div>
132
+ {/if}
133
+ </DropdownMenu.Content>
134
+ </DropdownMenu.Root>
135
+ {/if}
136
+ <h5>Champs pour date</h5>
137
+ <DropdownMenu.Root direction="bottom">
138
+ <DropdownMenu.Trigger slot="trigger">
139
+ <Button variant="secondary">
140
+ <h5>{dateField?.name ?? 'choisissez un champ pour date'}</h5>
141
+ </Button>
142
+ </DropdownMenu.Trigger>
143
+ <DropdownMenu.Content slot="content" width="250px">
144
+ {#if availedDateFields.length > 0}
145
+ {#each availedDateFields as field}
146
+ <Button
147
+ onClick={() => {
148
+ dateFieldId = field.id;
149
+ }}
150
+ variant="ghost"
151
+ style="width:100%;"><h5>{field.name}</h5></Button
152
+ >
153
+ {/each}
154
+ {:else}
155
+ <div class="nothing">
156
+ <h3>Aucun champs de date disponible</h3>
157
+ <p>L'entité {entity?.plural_name} ne contient pas de champs de date.</p>
158
+ </div>
159
+ {/if}
160
+ </DropdownMenu.Content>
161
+ </DropdownMenu.Root>
162
+ <h5>fréquence</h5>
163
+ <DropdownMenu.Root direction="bottom">
164
+ <DropdownMenu.Trigger slot="trigger">
165
+ <Button variant="secondary">
166
+ <h5>{frequency ? frequencyTextForConfig[frequency] : 'choisissez une fréquence'}</h5>
167
+ </Button>
168
+ </DropdownMenu.Trigger>
169
+ <DropdownMenu.Content slot="content" width="250px">
170
+ {#each frequencies as frequencyEl}
171
+ <Button
172
+ onClick={() => {
173
+ frequency = frequencyEl;
174
+ }}
175
+ variant="ghost"
176
+ style="width:100%;"><h5>{frequencyTextForConfig[frequencyEl]}</h5></Button
177
+ >
178
+ {/each}
179
+ </DropdownMenu.Content>
180
+ </DropdownMenu.Root>
181
+ {/if}
182
+
183
+ <Button onClick={handleSave}>
184
+ <h5>enregistrer</h5>
185
+ </Button>
186
+ </div>
187
+
188
+ <style>
189
+ .config {
190
+ display: flex;
191
+ flex-direction: column;
192
+ row-gap: var(--pad-m);
193
+ }
194
+
195
+ .nothing {
196
+ display: flex;
197
+ flex-direction: column;
198
+ row-gap: var(--pad-m);
199
+ padding: var(--pad-m);
200
+ }
201
+ </style>
@@ -0,0 +1,118 @@
1
+ <script>
2
+ import { sendRequest, Skeleton } from 'hamzus-ui';
3
+ import { frequencyText, operationText } from './text';
4
+ import { onMount } from 'svelte';
5
+
6
+ let {
7
+ id = '',
8
+ title = '',
9
+ entityId = '',
10
+ frequency = '',
11
+ valueFieldId = '',
12
+ dateFieldId = '',
13
+ operation = '',
14
+ } = $props()
15
+
16
+ let total = $state(null);
17
+ let previousTotal = $state(null);
18
+
19
+ onMount(async () => {
20
+ const request = await sendRequest({
21
+ path: '/get-widget-data',
22
+ data: {
23
+ widgetLinkId: id
24
+ }
25
+ });
26
+
27
+ total = request.data[0];
28
+ previousTotal = request.data[1];
29
+ });
30
+
31
+ function getRandomSizes() {
32
+ const sizes = [60, 120, 80];
33
+
34
+ return sizes[Math.floor(Math.random() * sizes.length)];
35
+ }
36
+
37
+ function getPercent() {
38
+ if (previousTotal === 0) {
39
+ if (total === 0) return 0;
40
+ return null; // augmentation non calculable
41
+ }
42
+ return ((total - previousTotal) / previousTotal) * 100;
43
+ }
44
+ </script>
45
+
46
+ <div class="kpi-container">
47
+ <div class="kpi">
48
+ <h4>{title}</h4>
49
+ {#if total === null}
50
+ <Skeleton style="width:{getRandomSizes()}px;height:32px;"></Skeleton>
51
+ {:else}
52
+ <h3>{total}</h3>
53
+ {/if}
54
+ <div class="bottom">
55
+ <h5>{frequencyText[frequency] ?? '-'} | {operationText[operation] ?? '-'}</h5>
56
+ <div class="right">
57
+ {#if total === null}
58
+ <h4 style="color: var(--green);">+</h4>
59
+ <Skeleton style="width:24px;height:16px;" />
60
+ <h4 style="color: var(--green);">%</h4>
61
+ {:else}
62
+ {@const percent = getPercent()}
63
+ {#if percent !== null}
64
+ <h4 style="color: var(--green);">+{getPercent()} %</h4>
65
+ <svg
66
+ width="24"
67
+ height="24"
68
+ viewBox="0 0 24 24"
69
+ fill="none"
70
+ xmlns="http://www.w3.org/2000/svg"
71
+ >
72
+ <path
73
+ opacity="0.4"
74
+ d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
75
+ fill="#34C759"
76
+ />
77
+ <path
78
+ d="M15.5295 10.9699L12.5295 7.96994C12.2395 7.67994 11.7595 7.67994 11.4695 7.96994L8.46945 10.9699C8.17945 11.2599 8.17945 11.7399 8.46945 12.0299C8.75945 12.3199 9.23945 12.3199 9.52945 12.0299L11.2495 10.3099V15.4999C11.2495 15.9099 11.5895 16.2499 11.9995 16.2499C12.4095 16.2499 12.7495 15.9099 12.7495 15.4999V10.3099L14.4695 12.0299C14.6195 12.1799 14.8095 12.2499 14.9995 12.2499C15.1895 12.2499 15.3795 12.1799 15.5295 12.0299C15.8195 11.7399 15.8195 11.2599 15.5295 10.9699Z"
79
+ fill="#34C759"
80
+ />
81
+ </svg>
82
+ {/if}
83
+ {/if}
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <style>
90
+ .kpi-container {
91
+ width: 100%;
92
+ height: 100%;
93
+ padding: var(--pad-m);
94
+ }
95
+
96
+ .kpi {
97
+ display: flex;
98
+ flex-direction: column;
99
+ row-gap: var(--pad-m);
100
+ padding: var(--pad-xxl);
101
+ width: 100%;
102
+ height: 100%;
103
+ background-color: var(--bg-blur);
104
+ border-radius: var(--radius-m);
105
+ }
106
+
107
+ .bottom {
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: space-between;
111
+ }
112
+
113
+ .right {
114
+ display: flex;
115
+ align-items: center;
116
+ column-gap: var(--pad-m);
117
+ }
118
+ </style>
@@ -0,0 +1,12 @@
1
+ import ConfigKPI from "./ConfigKPI.svelte";
2
+ import KPI from "./KPI.svelte";
3
+ import { createWidgetType } from "../../createWidgetType";
4
+
5
+ export default createWidgetType({
6
+ type: "KPI",
7
+ name: "KPI",
8
+ components: {
9
+ config: ConfigKPI,
10
+ component: KPI,
11
+ }
12
+ })
@@ -0,0 +1,22 @@
1
+
2
+
3
+ export const frequencyText = {
4
+ 'day': 'aujourd\'hui',
5
+ 'month': 'ce mois-ci',
6
+ 'year': 'cette année'
7
+ }
8
+
9
+ export const frequencyTextForConfig = {
10
+ 'day': 'par jour',
11
+ 'month': 'par mois',
12
+ 'year': 'par an'
13
+ }
14
+
15
+
16
+ export const operationText = {
17
+ 'count': 'nombre d\'entrée',
18
+ 'sum': 'somme',
19
+ 'avg': 'moyenne',
20
+ 'min': "min",
21
+ 'max': "max"
22
+ }