grav-svelte 0.0.22 → 0.0.24

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.
@@ -0,0 +1,178 @@
1
+ .crud-filters-container {
2
+ margin-bottom: 0;
3
+ margin-top: 0.25rem;
4
+ padding: 0.5rem;
5
+ }
6
+
7
+ @media (min-width: 640px) {
8
+ .crud-filters-container {
9
+ margin-bottom: 0;
10
+ margin-top: 0.25rem;
11
+ }
12
+ }
13
+
14
+ .filters-core {
15
+ display: flex;
16
+ width: 100%;
17
+ }
18
+
19
+ @media (min-width: 640px) {
20
+ .filters-core {
21
+ display: flex;
22
+ gap: 0.75rem;
23
+ }
24
+ }
25
+
26
+ .filters-header {
27
+ display: flex;
28
+ align-items: center;
29
+ gap: 0.75rem;
30
+ }
31
+
32
+ .filters-title {
33
+ font-weight: 700;
34
+ font-size: 1.5rem;
35
+ }
36
+
37
+ .filters-actions {
38
+ display: flex;
39
+ justify-content: flex-start;
40
+ align-items: center;
41
+ gap: 0.75rem;
42
+ width: 100%;
43
+ }
44
+
45
+ .filters-controls {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 0.75rem;
49
+ margin-top: 0.5rem;
50
+ }
51
+
52
+ @media (min-width: 640px) {
53
+ .filters-controls {
54
+ margin-top: 0;
55
+ }
56
+ }
57
+
58
+ .tooltip-container {
59
+ position: relative;
60
+ }
61
+
62
+ .tooltip {
63
+ position: absolute;
64
+ z-index: 10000;
65
+ padding: 0.25rem;
66
+ color: white;
67
+ font-size: 0.75rem;
68
+ background-color: #6b7280;
69
+ border-radius: 0.25rem;
70
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
71
+ top: -1.25rem;
72
+ left: -2.5rem;
73
+ }
74
+
75
+ .action-button {
76
+ display: flex;
77
+ justify-content: center;
78
+ align-items: center;
79
+ cursor: pointer;
80
+ border: 1px solid black;
81
+ width: 3rem;
82
+ height: 3rem;
83
+ border-radius: 0.5rem;
84
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
85
+ }
86
+
87
+ .filter-group {
88
+ display: inline-flex;
89
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
90
+ }
91
+
92
+ .filter-button {
93
+ display: inline-flex;
94
+ align-items: center;
95
+ cursor: pointer;
96
+ height: 3rem;
97
+ padding: 0.75rem 1rem;
98
+ font-size: 0.875rem;
99
+ font-weight: 500;
100
+ border: 1px solid black;
101
+ color: black;
102
+ border-radius: 0.5rem 0 0 0.5rem;
103
+ }
104
+
105
+ @media (min-width: 640px) {
106
+ .filter-button {
107
+ padding: 0.75rem 1rem;
108
+ font-size: 0.875rem;
109
+ }
110
+ }
111
+
112
+ .filter-button-right {
113
+ border-radius: 0 0.5rem 0.5rem 0;
114
+ }
115
+
116
+ .page-size-input {
117
+ width: 4rem;
118
+ }
119
+
120
+ .apply-filter-button {
121
+ display: inline-flex;
122
+ height: 3rem;
123
+ cursor: pointer;
124
+ align-items: center;
125
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
126
+ padding: 0.75rem 1rem;
127
+ font-size: 0.875rem;
128
+ font-weight: 500;
129
+ border: 1px solid black;
130
+ color: black;
131
+ border-radius: 0.5rem;
132
+ }
133
+
134
+ @media (min-width: 640px) {
135
+ .apply-filter-button {
136
+ padding: 0.75rem 1rem;
137
+ font-size: 0.875rem;
138
+ }
139
+ }
140
+
141
+ .filters-grid {
142
+ display: grid;
143
+ gap: 0.75rem;
144
+ margin-top: 1rem;
145
+ grid-template-columns: repeat(1, 1fr);
146
+ }
147
+
148
+ @media (min-width: 640px) {
149
+ .filters-grid {
150
+ grid-template-columns: repeat(5, 1fr);
151
+ }
152
+ }
153
+
154
+ .filter-item {
155
+ position: relative;
156
+ z-index: 0;
157
+ margin: auto 0;
158
+ }
159
+
160
+ .filter-item-number {
161
+ z-index: 50;
162
+ }
163
+
164
+ .filter-item-datetime {
165
+ z-index: 50;
166
+ }
167
+
168
+ .filter-item-date {
169
+ z-index: 50;
170
+ }
171
+
172
+ .filter-item-select {
173
+ z-index: 50;
174
+ }
175
+
176
+ .filter-item-bool {
177
+ margin: auto 0;
178
+ }
@@ -6,6 +6,7 @@
6
6
  import InputFormDateAndHours from "../Inputs/InputFormDateAndHours.svelte";
7
7
  import InputFormDate from "../Inputs/InputFormDate.svelte";
8
8
  import { slide } from "svelte/transition";
9
+ import "./CrudFilters.css";
9
10
 
10
11
  import { createEventDispatcher } from "svelte";
11
12
  import type { FiltrosI } from "./interfaces.js";
@@ -14,15 +15,22 @@
14
15
  let showTooltip = "";
15
16
  export let PageSize = 50;
16
17
  let localPageSize = 50;
18
+ let localPageSizeStr = "50";
17
19
  let showFilters = false;
18
20
 
19
21
  export let Filtros: FiltrosI[];
20
22
  export let showAddButton: boolean = true;
21
23
  export let showImportButton: boolean = true;
22
24
 
23
- // Ensure localPageSize is never 0
24
- $: if (localPageSize == 0) {
25
- localPageSize = 1;
25
+ // Convert string to number and ensure it's never 0
26
+ $: {
27
+ const num = parseInt(localPageSizeStr);
28
+ if (!isNaN(num)) {
29
+ const newValue = num === 0 ? 1 : num;
30
+ if (newValue !== localPageSize) {
31
+ localPageSize = newValue;
32
+ }
33
+ }
26
34
  }
27
35
 
28
36
  function clearFilters() {
@@ -43,10 +51,7 @@
43
51
  }
44
52
 
45
53
  // Handle keydown event
46
- function handleKeydown(event) {
47
- // Check if the event target is a textarea or input
48
- // const isInputElement = event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT';
49
-
54
+ function handleKeydown(event: KeyboardEvent) {
50
55
  if (event.key === "Enter") {
51
56
  event.preventDefault();
52
57
  event.stopPropagation();
@@ -55,10 +60,17 @@
55
60
  }
56
61
 
57
62
  // Handle click to focus the modal
58
- function handleClick(event) {
63
+ function handleClick(event: MouseEvent) {
59
64
  // Only focus if clicking directly on the modal container
60
65
  if (event.target === event.currentTarget) {
61
- event.target.focus();
66
+ (event.target as HTMLElement).focus();
67
+ }
68
+ }
69
+
70
+ function handlePageSizeInput(event: Event) {
71
+ const input = event.target as HTMLInputElement;
72
+ if (input) {
73
+ localPageSizeStr = input.value;
62
74
  }
63
75
  }
64
76
  </script>
@@ -66,31 +78,29 @@
66
78
  <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
67
79
  <!-- svelte-ignore a11y_no_static_element_interactions -->
68
80
  <div
69
- class="sm:mb-0 sm:mt-1 p-2"
81
+ class="crud-filters-container"
70
82
  on:keydown={handleKeydown}
71
83
  on:click={handleClick}
72
84
  tabindex="0"
73
85
  >
74
86
  <!-- Filtros CORE -->
75
87
  <!--------------------------------------------------------------------->
76
- <div class="sm:flex sm:space-x-3 w-full">
77
- <div class=" flex items-center space-x-3">
78
- <h1 class=" font-bold text-2xl">Transacciones</h1>
79
- <div class="flex justify-start items-center space-x-3 w-full">
88
+ <div class="filters-core">
89
+ <div class="filters-header">
90
+ <h1 class="filters-title">Transacciones</h1>
91
+ <div class="filters-actions">
80
92
  {#if showAddButton}
81
93
  <div>
82
- <div class="relative">
94
+ <div class="tooltip-container">
83
95
  {#if showTooltip == "Agregar"}
84
- <div
85
- class="absolute z-[10000] p-1 text-white text-xs bg-gray-500 rounded shadow -top-5 -left-10"
86
- >
96
+ <div class="tooltip">
87
97
  Agregar
88
98
  </div>
89
99
  {/if}
90
100
  </div>
91
101
  <!-- svelte-ignore a11y_consider_explicit_label -->
92
102
  <button
93
- class=" flex justify-center items-center cursor-pointer border border-black w-12 h-12 rounded-lg shadow-md"
103
+ class="action-button"
94
104
  type="button"
95
105
  on:mouseenter={() => (showTooltip = "Agregar")}
96
106
  on:mouseleave={() => (showTooltip = "")}
@@ -115,18 +125,16 @@
115
125
  {/if}
116
126
  {#if showImportButton}
117
127
  <div>
118
- <div class="relative">
128
+ <div class="tooltip-container">
119
129
  {#if showTooltip == "Importar"}
120
- <div
121
- class="absolute z-[10000] p-1 text-white text-xs bg-gray-500 rounded shadow -top-5 -left-10"
122
- >
130
+ <div class="tooltip">
123
131
  Importar Excel
124
132
  </div>
125
133
  {/if}
126
134
  </div>
127
135
  <!-- svelte-ignore a11y_consider_explicit_label -->
128
136
  <button
129
- class=" flex justify-center items-center border border-black w-12 h-12 rounded-lg shadow-md"
137
+ class="action-button"
130
138
  type="button"
131
139
  on:mouseenter={() => (showTooltip = "Importar")}
132
140
  on:mouseleave={() => (showTooltip = "")}
@@ -153,15 +161,15 @@
153
161
  {/if}
154
162
  </div>
155
163
  </div>
156
- <div class=" flex items-center space-x-3 mt-2 sm:mt-0">
164
+ <div class="filters-controls">
157
165
  <!-- Show Filters Button -->
158
- <div class="inline-flex shadow-sm" role="group">
166
+ <div class="filter-group" role="group">
159
167
  {#if Filtros && Filtros.length > 0}
160
168
  {#if showFilters}
161
169
  <button
162
170
  type="button"
163
171
  on:click={() => (showFilters = !showFilters)}
164
- class="inline-flex items-center cursor-pointer h-12 sm:px-4 sm:py-3 px-3 py-2 sm:text-sm text-xs font-medium border border-black text-black rounded-l-lg"
172
+ class="filter-button"
165
173
  >
166
174
  <svg
167
175
  xmlns="http://www.w3.org/2000/svg"
@@ -185,7 +193,7 @@
185
193
  <button
186
194
  type="button"
187
195
  on:click={() => (showFilters = !showFilters)}
188
- class="inline-flex items-center cursor-pointer h-12 sm:px-4 sm:py-3 px-3 py-2 sm:text-sm text-xs font-medium border border-black text-black rounded-l-lg"
196
+ class="filter-button"
189
197
  >
190
198
  <svg
191
199
  xmlns="http://www.w3.org/2000/svg"
@@ -208,12 +216,9 @@
208
216
  {/if}
209
217
 
210
218
  <!-- Btn de limpiar filtros -->
211
-
212
- <div class="relative">
219
+ <div class="tooltip-container">
213
220
  {#if showTooltip == "Borrar"}
214
- <div
215
- class="absolute z-[10000] p-1 text-white text-xs bg-gray-500 rounded shadow -top-5 -left-10"
216
- >
221
+ <div class="tooltip">
217
222
  Borrar filtro
218
223
  </div>
219
224
  {/if}
@@ -224,7 +229,7 @@
224
229
  on:click={() => clearFilters()}
225
230
  on:mouseenter={() => (showTooltip = "Borrar")}
226
231
  on:mouseleave={() => (showTooltip = "nada")}
227
- class="inline-flex items-center cursor-pointer sm:px-4 sm:py-3 px-3 py-2 sm:text-sm text-xs font-medium border border-black rounded-r-lg text-black"
232
+ class="filter-button filter-button-right"
228
233
  >
229
234
  <svg
230
235
  xmlns="http://www.w3.org/2000/svg"
@@ -247,21 +252,20 @@
247
252
  </div>
248
253
 
249
254
  <!-- Filtro 2 -->
250
- <div class=" w-16">
255
+ <div class="page-size-input">
251
256
  <InputFormText
252
257
  label="Mostrando:"
253
- bind:valueVar={localPageSize}
258
+ valueVar={localPageSizeStr}
259
+ on:input={handlePageSizeInput}
254
260
  />
255
261
  </div>
256
262
  <!-- /Filtro 2 -->
257
263
 
258
264
  <!-- Btn de aplicar filtro (siempre visible) -->
259
- <div class="inline-flex rounded-md shadow-sm" role="group">
260
- <div class="relative">
265
+ <div class="filter-group" role="group">
266
+ <div class="tooltip-container">
261
267
  {#if showTooltip == "Aplicar"}
262
- <div
263
- class="absolute z-[10000] p-1 transition-all ease-in duration-200 text-white text-xs bg-gray-500 rounded shadow -top-5 -left-10"
264
- >
268
+ <div class="tooltip">
265
269
  Aplicar filtro
266
270
  </div>
267
271
  {/if}
@@ -271,7 +275,7 @@
271
275
  on:click={() => actualizarFiltro()}
272
276
  on:mouseenter={() => (showTooltip = "Aplicar")}
273
277
  on:mouseleave={() => (showTooltip = "nada")}
274
- class="inline-flex h-12 cursor-pointer items-center shadow-md sm:px-4 sm:py-3 px-3 py-2 sm:text-sm text-xs font-medium border border-black text-black rounded-lg"
278
+ class="apply-filter-button"
275
279
  >Filtrar</button
276
280
  >
277
281
  </div>
@@ -281,40 +285,40 @@
281
285
  <!-- Filtros Dynamic -->
282
286
  {#if showFilters}
283
287
  <div
284
- class="grid sm:grid-cols-5 grid-cols-1 gap-3 mt-4"
288
+ class="filters-grid"
285
289
  transition:slide|local={{ duration: 300, delay: 100 }}
286
290
  >
287
291
  {#each Filtros as { tipo, label, options }, i}
288
292
  {#if tipo == "text"}
289
- <div class="relative z-0 my-auto">
293
+ <div class="filter-item">
290
294
  <InputFormText
291
295
  {label}
292
296
  bind:valueVar={Filtros[i].value}
293
297
  />
294
298
  </div>
295
299
  {:else if tipo == "number"}
296
- <div class="z-50">
300
+ <div class="filter-item filter-item-number">
297
301
  <InputFormNumber
298
302
  {label}
299
303
  bind:valueVar={Filtros[i].value}
300
304
  />
301
305
  </div>
302
306
  {:else if tipo == "datetime"}
303
- <div class="z-50">
307
+ <div class="filter-item filter-item-datetime">
304
308
  <InputFormDateAndHours
305
309
  {label}
306
310
  bind:valueVar={Filtros[i].value}
307
311
  />
308
312
  </div>
309
313
  {:else if tipo == "date"}
310
- <div class="z-50">
314
+ <div class="filter-item filter-item-date">
311
315
  <InputFormDate
312
316
  {label}
313
317
  bind:valueVar={Filtros[i].value}
314
318
  />
315
319
  </div>
316
320
  {:else if tipo == "select"}
317
- <div class="z-50">
321
+ <div class="filter-item filter-item-select">
318
322
  <InputFormSelect
319
323
  {label}
320
324
  res={options}
@@ -322,7 +326,7 @@
322
326
  />
323
327
  </div>
324
328
  {:else if tipo == "bool"}
325
- <div class="my-auto">
329
+ <div class="filter-item filter-item-bool">
326
330
  <InputFormBool
327
331
  {label}
328
332
  bind:valueVar={Filtros[i].value}
@@ -1,4 +1,5 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
+ import "./CrudFilters.css";
2
3
  import type { FiltrosI } from "./interfaces.js";
3
4
  declare const __propDef: {
4
5
  props: {
@@ -38,188 +38,313 @@
38
38
  }
39
39
  </script>
40
40
 
41
- <div
42
- class="relative flex flex-col min-w-0 break-words z-30 w-full mb-3 shadow-lg rounded mt-3 p-1 bg-white"
43
- >
44
- <div class="not-prose relative bg-slate-50 rounded-xl overflow-hidden">
45
- <div class="relative rounded-xl overflow-auto">
46
- <div class=" bg-white shadow-xl overflow-hidden">
47
- <div class="overflow-scroll grid max-h-[80vh]">
48
- <table
49
- class="items-center w-full bg-transparent border-collapse shadow-xs table-auto"
50
- bind:this={tablaExport}
51
- >
52
- <thead class="shadow-lg sticky top-0 z-20 bg-white">
53
- <tr class="relative">
54
- {#each tableHeaders as tableHeader}
55
- {#if tableHeader.biSort == false}
56
- <th
57
- class="px-3 align-middle cursor-not-allowed text-center border border-solid py-1 text-xs uppercase border-l-0 border-r whitespace-nowrap font-semibold bg-blueGray-50 text-blueGray-500 border-blueGray-100"
58
- >
59
- {tableHeader.titulo}
60
- </th>
41
+ <div class="table-container">
42
+ <div class="table-scroll">
43
+ <table class="data-table" bind:this={tablaExport}>
44
+ <thead class="table-header">
45
+ <tr>
46
+ {#each tableHeaders as tableHeader, index}
47
+ {#if tableHeader.biSort == false}
48
+ <th
49
+ class="table-header-cell {index == 0
50
+ ? 'borderleft'
51
+ : ''} non-sortable"
52
+ >
53
+ {tableHeader.titulo}
54
+ </th>
55
+ {:else}
56
+ <th
57
+ on:click={() => dispatchSort(tableHeader.campo)}
58
+ class="table-header-cell {index == 0
59
+ ? 'borderleft'
60
+ : ''} sortable"
61
+ >
62
+ <h1>{tableHeader.titulo}</h1>
63
+ {#if selectedSort == tableHeader.campo}
64
+ {#if selectedAscOrDesc == "asc"}
65
+ <div class="sort-icon">
66
+ <svg
67
+ xmlns="http://www.w3.org/2000/svg"
68
+ width="16"
69
+ height="16"
70
+ viewBox="0 0 24 24"
71
+ fill="none"
72
+ stroke="currentColor"
73
+ stroke-width="2"
74
+ stroke-linecap="round"
75
+ stroke-linejoin="round"
76
+ class="sort-arrow"
77
+ >
78
+ <polyline
79
+ points="6 9 12 15 18 9"
80
+ ></polyline>
81
+ </svg>
82
+ </div>
61
83
  {:else}
62
- <th
63
- on:click={() =>
64
- dispatchSort(tableHeader.campo)}
65
- class="px-3 border-r cursor-pointer align-middle border border-solid py-1 text-xs uppercase border-l-0 whitespace-nowrap font-semibold text-left bg-blueGray-50 text-blueGray-500 border-blueGray-100"
66
- >
67
- <h1 class="relative">
68
- {tableHeader.titulo}
69
- </h1>
70
- {#if selectedSort == tableHeader.campo}
71
- {#if selectedAscOrDesc == "asc"}
72
- <div
73
- class="flex items-center justify-center"
74
- >
75
- <svg
76
- xmlns="http://www.w3.org/2000/svg"
77
- width="16"
78
- height="16"
79
- viewBox="0 0 24 24"
80
- fill="none"
81
- stroke="currentColor"
82
- stroke-width="2"
83
- stroke-linecap="round"
84
- stroke-linejoin="round"
85
- class="ml-1"
86
- >
87
- <polyline
88
- points="6 9 12 15 18 9"
89
- ></polyline>
90
- </svg>
91
- </div>
92
- {:else}
93
- <div
94
- class="flex items-center justify-center"
95
- >
96
- <svg
97
- xmlns="http://www.w3.org/2000/svg"
98
- width="16"
99
- height="16"
100
- viewBox="0 0 24 24"
101
- fill="none"
102
- stroke="currentColor"
103
- stroke-width="2"
104
- stroke-linecap="round"
105
- stroke-linejoin="round"
106
- class="ml-1"
107
- >
108
- <polyline
109
- points="18 15 12 9 6 15"
110
- ></polyline>
111
- </svg>
112
- </div>
113
- {/if}
114
- {/if}
115
- </th>
84
+ <div class="sort-icon">
85
+ <svg
86
+ xmlns="http://www.w3.org/2000/svg"
87
+ width="16"
88
+ height="16"
89
+ viewBox="0 0 24 24"
90
+ fill="none"
91
+ stroke="currentColor"
92
+ stroke-width="2"
93
+ stroke-linecap="round"
94
+ stroke-linejoin="round"
95
+ class="sort-arrow"
96
+ >
97
+ <polyline
98
+ points="18 15 12 9 6 15"
99
+ ></polyline>
100
+ </svg>
101
+ </div>
116
102
  {/if}
117
- {/each}
118
- </tr>
119
- </thead>
120
-
121
- {#if todosLosRegistros && !loading}
122
- <tbody>
123
- {#each todosLosRegistros as item, index}
124
- <tr
125
- class="border-b-2 cursor-pointer transition-colors duration-200 {selectedRowId ===
126
- index
127
- ? 'bg-gray-100'
128
- : 'hover:bg-gray-50'}"
129
- on:click={() => handleRowClick(index)}
103
+ {/if}
104
+ </th>
105
+ {/if}
106
+ {/each}
107
+ </tr>
108
+ </thead>
109
+
110
+ {#if todosLosRegistros && !loading}
111
+ <tbody>
112
+ {#each todosLosRegistros as item, index}
113
+ <tr
114
+ class="table-row {selectedRowId === index
115
+ ? 'selected'
116
+ : ''}"
117
+ on:click={() => handleRowClick(index)}
118
+ >
119
+ {#each tableHeaders as tableBodyItem, i}
120
+ {#if tableBodyItem.tipo == "Text"}
121
+ <td
122
+ class="table-cell {i == 0
123
+ ? 'sticky-cell'
124
+ : ''}"
130
125
  >
131
- {#each tableHeaders as tableBodyItem, i}
132
- {#if tableBodyItem.tipo == "Text"}
133
- <td
134
- class="border-t-0 {i == 0
135
- ? 'sticky bg-white'
136
- : ''} {selectedRowId ===
137
- index
138
- ? 'bg-gray-100'
139
- : 'hover:bg-gray-50'} z-10 left-0 align-middle border-l-0 border-r-0 whitespace-nowrap"
140
- >
141
- <p
142
- class=" pl-1 whitespace-normal break-word sm:text-base text-xs {tableBodyItem.biBold
143
- ? 'font-bold'
144
- : ''} text-blueGray-500"
145
- >
146
- {item[
147
- tableBodyItem.campo
148
- ] ?? ""}
149
- </p>
150
- </td>
151
- {:else if tableBodyItem.tipo == "Number"}
152
- <td
153
- class="border-t-0 {i == 0
154
- ? 'sticky bg-white'
155
- : ''} {selectedRowId ===
156
- index
157
- ? 'bg-gray-100'
158
- : 'hover:bg-gray-50'} z-10 left-0 align-middle border-l-0 border-r-0 whitespace-nowrap"
159
- >
160
- <p
161
- class=" pl-1 whitespace-normal break-word sm:text-base text-xs {tableBodyItem.biBold
162
- ? 'font-bold'
163
- : ''} text-blueGray-500"
164
- >
165
- {item[
166
- tableBodyItem.campo
167
- ] ?? ""}
168
- </p>
169
- </td>
170
- {:else if tableBodyItem.tipo == "Buttons"}
171
- <CrudTableButtons
172
- id={item[
173
- tableBodyItem.campo
174
- ]}
175
- buttonsConfig={tableBodyItem.buttonsConfig ??
176
- []}
177
- />
178
- {/if}
179
- {/each}
180
- </tr>
181
- {/each}
182
- </tbody>
183
- {:else if !loading}
184
- <tbody>
185
- <tr>
126
+ <p
127
+ class="cell-content {tableBodyItem.biBold
128
+ ? 'bold'
129
+ : ''}"
130
+ >
131
+ {item[tableBodyItem.campo] ?? ""}
132
+ </p>
133
+ </td>
134
+ {:else if tableBodyItem.tipo == "Number"}
186
135
  <td
187
- colspan={tableHeaders.length}
188
- class="text-center py-4"
136
+ class="table-cell {i == 0
137
+ ? 'sticky-cell'
138
+ : ''}"
189
139
  >
190
- No hay datos disponibles
140
+ <p
141
+ class="cell-content {tableBodyItem.biBold
142
+ ? 'bold'
143
+ : ''}"
144
+ >
145
+ {item[tableBodyItem.campo] ?? ""}
146
+ </p>
191
147
  </td>
192
- </tr>
193
- </tbody>
194
- {/if}
195
- </table>
196
- {#if loading}
197
- <div class="mx-auto w-full rounded-md p-4">
198
- <div class="flex animate-pulse space-x-4">
199
- <div class="flex-1 space-y-6 py-1">
200
- <div class="space-y-4">
201
- <div class="h-4 rounded bg-gray-200" />
202
- <div class="h-4 rounded bg-gray-200" />
203
- <div class="h-4 rounded bg-gray-200" />
204
- <div class="h-4 rounded bg-gray-200" />
205
- <div class="h-4 rounded bg-gray-200" />
206
- <div class="h-4 rounded bg-gray-200" />
207
- <div class="h-4 rounded bg-gray-200" />
208
- <div class="h-4 rounded bg-gray-200" />
209
- <div class="h-4 rounded bg-gray-200" />
210
- <div class="h-4 rounded bg-gray-200" />
211
- <div class="h-4 rounded bg-gray-200" />
212
- <div class="h-4 rounded bg-gray-200" />
213
- <div class="h-4 rounded bg-gray-200" />
214
- <div class="h-4 rounded bg-gray-200" />
215
- <div class="h-4 rounded bg-gray-200" />
216
- </div>
217
- </div>
218
- </div>
219
- </div>
220
- {/if}
148
+ {:else if tableBodyItem.tipo == "Buttons"}
149
+ <CrudTableButtons
150
+ id={item[tableBodyItem.campo]}
151
+ buttonsConfig={tableBodyItem.buttonsConfig ??
152
+ []}
153
+ />
154
+ {/if}
155
+ {/each}
156
+ </tr>
157
+ {/each}
158
+ </tbody>
159
+ {:else if !loading}
160
+ <tbody>
161
+ <tr>
162
+ <td colspan={tableHeaders.length} class="no-data">
163
+ No hay datos disponibles
164
+ </td>
165
+ </tr>
166
+ </tbody>
167
+ {/if}
168
+ </table>
169
+ {#if loading}
170
+ <div class="loading-container">
171
+ <div class="loading-animation">
172
+ <div class="loading-line"></div>
173
+ <div class="loading-line"></div>
174
+ <div class="loading-line"></div>
175
+ <div class="loading-line"></div>
176
+ <div class="loading-line"></div>
177
+ <div class="loading-line"></div>
178
+ <div class="loading-line"></div>
179
+ <div class="loading-line"></div>
180
+ <div class="loading-line"></div>
181
+ <div class="loading-line"></div>
182
+ <div class="loading-line"></div>
183
+ <div class="loading-line"></div>
184
+ <div class="loading-line"></div>
185
+ <div class="loading-line"></div>
186
+ <div class="loading-line"></div>
221
187
  </div>
222
188
  </div>
223
- </div>
189
+ {/if}
224
190
  </div>
225
191
  </div>
192
+
193
+ <style>
194
+ .table-container {
195
+ position: relative;
196
+ display: flex;
197
+ flex-direction: column;
198
+ min-width: 0;
199
+ word-break: break-word;
200
+ z-index: 30;
201
+ width: 100%;
202
+ margin-bottom: 0.75rem;
203
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
204
+ margin-top: 0.75rem;
205
+ background-color: white;
206
+ }
207
+
208
+ .table-scroll {
209
+ overflow: auto;
210
+ max-height: 80vh;
211
+ }
212
+
213
+ .data-table {
214
+ width: 100%;
215
+ background-color: transparent;
216
+ border-collapse: collapse;
217
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
218
+ table-layout: auto;
219
+ }
220
+
221
+ .table-header {
222
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
223
+ position: sticky;
224
+ top: 0;
225
+ z-index: 20;
226
+ background-color: white;
227
+ }
228
+
229
+ .table-header-cell {
230
+ padding: 0.75rem;
231
+ text-align: center;
232
+ border: 1px solid #e0e0e0;
233
+ padding-top: 0.25rem;
234
+ padding-bottom: 0.25rem;
235
+ font-size: 0.75rem;
236
+ text-transform: uppercase;
237
+ white-space: nowrap;
238
+ font-weight: 600;
239
+ background-color: #f5f5f5;
240
+ color: #4a4a4a;
241
+ border-left: 0;
242
+ border-right: 1px solid #e0e0e0;
243
+ }
244
+
245
+ .non-sortable {
246
+ cursor: not-allowed;
247
+ }
248
+
249
+ .sortable {
250
+ cursor: pointer;
251
+ text-align: left;
252
+ }
253
+
254
+ .borderleft {
255
+ border-left: 1px solid #e0e0e0;
256
+ }
257
+
258
+ .sort-icon {
259
+ display: flex;
260
+ align-items: center;
261
+ justify-content: center;
262
+ }
263
+
264
+ .sort-arrow {
265
+ margin-left: 0.25rem;
266
+ }
267
+
268
+ .table-row {
269
+ border-bottom: 2px solid #e0e0e0;
270
+ cursor: pointer;
271
+ transition: background-color 0.2s;
272
+ }
273
+
274
+ .table-row:hover {
275
+ background-color: #f5f5f5;
276
+ }
277
+
278
+ .table-row.selected {
279
+ background-color: #e8e8e8;
280
+ }
281
+
282
+ .table-cell {
283
+ border-top: 0;
284
+ border-left: 0;
285
+ border-right: 0;
286
+ white-space: nowrap;
287
+ vertical-align: middle;
288
+ z-index: 10;
289
+ }
290
+
291
+ .sticky-cell {
292
+ position: sticky;
293
+ background-color: white;
294
+ left: 0;
295
+ }
296
+
297
+ .cell-content {
298
+ padding-left: 0.25rem;
299
+ white-space: normal;
300
+ word-break: break-word;
301
+ font-size: 0.875rem;
302
+ color: #4a4a4a;
303
+ }
304
+
305
+ .cell-content.bold {
306
+ font-weight: bold;
307
+ }
308
+
309
+ .no-data {
310
+ text-align: center;
311
+ padding: 1rem 0;
312
+ }
313
+
314
+ .loading-container {
315
+ margin: 0 auto;
316
+ width: 100%;
317
+ padding: 1rem;
318
+ }
319
+
320
+ .loading-animation {
321
+ display: flex;
322
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
323
+ }
324
+
325
+ .loading-line {
326
+ height: 1rem;
327
+ border-radius: 0.25rem;
328
+ background-color: #e0e0e0;
329
+ margin-bottom: 0.5rem;
330
+ width: 100%;
331
+ flex: 1;
332
+ margin: 0 0.5rem;
333
+ }
334
+
335
+ @keyframes pulse {
336
+ 0%,
337
+ 100% {
338
+ opacity: 1;
339
+ }
340
+ 50% {
341
+ opacity: 0.5;
342
+ }
343
+ }
344
+
345
+ @media (min-width: 640px) {
346
+ .cell-content {
347
+ font-size: 1rem;
348
+ }
349
+ }
350
+ </style>
@@ -6,14 +6,12 @@
6
6
  let showTooltip = "";
7
7
  </script>
8
8
 
9
- <td class="text-xs text-center">
10
- <div class="inline-flex rounded-md shadow-sm" role="group">
9
+ <td class="table-cell">
10
+ <div class="button-group" role="group">
11
11
  {#each buttonsConfig as button, i}
12
- <div class="relative">
12
+ <div class="tooltip-container">
13
13
  {#if showTooltip == button.tooltip}
14
- <div
15
- class="absolute z-[10000] p-1 text-white text-xs bg-gray-500 rounded shadow -top-6 -right-10"
16
- >
14
+ <div class="tooltip">
17
15
  {button.tooltip}
18
16
  </div>
19
17
  {/if}
@@ -24,15 +22,68 @@
24
22
  on:mouseenter={() => (showTooltip = button.tooltip)}
25
23
  on:mouseleave={() => (showTooltip = "")}
26
24
  type="button"
27
- class="inline-flex items-center cursor-pointer sm:px-2 sm:py-1 px-3 py-2 min-h-6 min-w-5 sm:text-xs text-xs border border-gray-200 text-white {i ==
28
- 0
29
- ? 'rounded-l-lg'
30
- : i == buttonsConfig.length - 1
31
- ? 'rounded-r-lg'
32
- : ''} {button.color} "
25
+ class="action-buttons-group {i == 0 ? 'rounded-left' : i == buttonsConfig.length - 1 ? 'rounded-right' : ''} {button.color}"
33
26
  >
34
27
  <i class={button.icon}> </i>
35
28
  </button>
36
29
  {/each}
37
30
  </div>
38
31
  </td>
32
+
33
+ <style>
34
+ .table-cell {
35
+ font-size: 0.75rem;
36
+ text-align: center;
37
+ }
38
+
39
+ .button-group {
40
+ display: inline-flex;
41
+ border-radius: 0.375rem;
42
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
43
+ }
44
+
45
+ .tooltip-container {
46
+ position: relative;
47
+ }
48
+
49
+ .tooltip {
50
+ position: absolute;
51
+ z-index: 10000;
52
+ padding: 0.25rem;
53
+ color: white;
54
+ font-size: 0.75rem;
55
+ background-color: #6b7280;
56
+ border-radius: 0.25rem;
57
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
58
+ top: -1.5rem;
59
+ right: -2.5rem;
60
+ }
61
+
62
+ .action-buttons-group {
63
+ display: inline-flex;
64
+ align-items: center;
65
+ cursor: pointer;
66
+ padding: 0.5rem 0.75rem;
67
+ min-height: 1.5rem;
68
+ min-width: 1.25rem;
69
+ font-size: 0.75rem;
70
+ border: 1px solid #e5e7eb;
71
+ color: white;
72
+ }
73
+
74
+ @media (min-width: 640px) {
75
+ .action-button {
76
+ padding: 0.25rem 0.5rem;
77
+ }
78
+ }
79
+
80
+ .rounded-left {
81
+ border-top-left-radius: 0.5rem;
82
+ border-bottom-left-radius: 0.5rem;
83
+ }
84
+
85
+ .rounded-right {
86
+ border-top-right-radius: 0.5rem;
87
+ border-bottom-right-radius: 0.5rem;
88
+ }
89
+ </style>
@@ -65,7 +65,7 @@
65
65
  }
66
66
  </script>
67
67
 
68
- <div class=" min-h-screen">
68
+ <div class="crud-wrapper">
69
69
  <CrudFilters
70
70
  bind:PageSize
71
71
  bind:Filtros
@@ -87,20 +87,49 @@
87
87
  on:pageChange={handlePageChange}
88
88
  />
89
89
 
90
- <div class="flex gap-4 mt-4 justify-end">
90
+ <div class="export-buttons">
91
91
  <button
92
92
  type="button"
93
93
  on:click={() => handleExport("excel")}
94
- class="bg-green-600 text-center cursor-pointer text-white p-4 rounded"
94
+ class="export-button excel-button"
95
95
  >
96
96
  <i class="fas fa-file-excel pr-3"> </i>EXCEL
97
97
  </button>
98
98
  <button
99
99
  type="button"
100
100
  on:click={() => handleExport("pdf")}
101
- class="bg-orange-500 text-center cursor-pointer text-white p-4 rounded"
101
+ class="export-button pdf-button"
102
102
  >
103
103
  <i class="far fa-file-pdf pr-3"> </i>PDF
104
104
  </button>
105
105
  </div>
106
106
  </div>
107
+
108
+ <style>
109
+ .crud-wrapper {
110
+ min-height: 100vh;
111
+ }
112
+
113
+ .export-buttons {
114
+ display: flex;
115
+ gap: 1rem;
116
+ margin-top: 1rem;
117
+ justify-content: flex-end;
118
+ }
119
+
120
+ .export-button {
121
+ text-align: center;
122
+ cursor: pointer;
123
+ color: white;
124
+ padding: 1rem;
125
+ border-radius: 0.25rem;
126
+ }
127
+
128
+ .excel-button {
129
+ background-color: #16a34a; /* green-600 */
130
+ }
131
+
132
+ .pdf-button {
133
+ background-color: #f97316; /* orange-500 */
134
+ }
135
+ </style>
@@ -6,12 +6,13 @@
6
6
  export let nombreIcono;
7
7
  export let notifiacion: number | null;
8
8
  export let permiso: boolean;
9
+ export let baseRoute: string;
9
10
  </script>
10
11
 
11
12
  {#if permiso == true}
12
13
  <div class="sidebar-item">
13
14
  <a
14
- href="{base}/{nombreRuta}"
15
+ href="{base}/{baseRoute}/{nombreRuta}"
15
16
  class="sidebar-link"
16
17
  >
17
18
  <i class="sidebar-icon {nombreIcono}"></i>
@@ -6,6 +6,7 @@ declare const __propDef: {
6
6
  nombreIcono: any;
7
7
  notifiacion: number | null;
8
8
  permiso: boolean;
9
+ baseRoute: string;
9
10
  };
10
11
  events: {
11
12
  [evt: string]: CustomEvent<any>;
@@ -7,6 +7,7 @@
7
7
  export let sections: SidebarSection[];
8
8
  export let brandName: string;
9
9
  export let brandLink: string;
10
+ export let baseRoute: string;
10
11
  export let showLogout: boolean = true;
11
12
  export let logoutLink: string = "/";
12
13
  export let customClass: string = "";
@@ -97,6 +98,7 @@
97
98
  nombreIcono={module.icono}
98
99
  notifiacion={module.notifiacion ?? null}
99
100
  permiso={module.permiso ?? true}
101
+ baseRoute={baseRoute}
100
102
  />
101
103
  {/each}
102
104
  {/if}
@@ -6,6 +6,7 @@ declare const __propDef: {
6
6
  sections: SidebarSection[];
7
7
  brandName: string;
8
8
  brandLink: string;
9
+ baseRoute: string;
9
10
  showLogout?: boolean;
10
11
  logoutLink?: string;
11
12
  customClass?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grav-svelte",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "A collection of Svelte components",
5
5
  "license": "MIT",
6
6
  "scripts": {