webspresso 0.0.72 → 0.0.74
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.
- package/README.md +4 -2
- package/bin/commands/upgrade.js +146 -0
- package/bin/webspresso.js +2 -0
- package/package.json +1 -1
- package/plugins/admin-panel/app.js +109 -0
- package/plugins/admin-panel/components.js +111 -111
- package/plugins/admin-panel/modules/dashboard.js +16 -13
- package/plugins/admin-panel/modules/user-management.js +32 -11
- package/plugins/data-exchange/export-xlsx.js +3 -0
- package/plugins/data-exchange/record-selection.js +21 -5
- package/plugins/site-analytics/admin-component.js +88 -78
- package/src/file-router.js +80 -4
- package/src/server.js +2 -0
- package/templates/skills/webspresso-usage/SKILL.md +5 -2
|
@@ -32,6 +32,38 @@ const api = {
|
|
|
32
32
|
delete(path) { return this.request(path, { method: 'DELETE' }); },
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
/** POST /data-exchange/export/:model — validates JSON errors vs .xlsx blob */
|
|
36
|
+
async function downloadDataExchangeXlsx(modelName, payload) {
|
|
37
|
+
const adminPath = window.__ADMIN_PATH__ || '/_admin';
|
|
38
|
+
const res = await fetch(adminPath + '/api/data-exchange/export/' + modelName, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
credentials: 'include',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify(payload),
|
|
43
|
+
});
|
|
44
|
+
const ct = (res.headers.get('content-type') || '').toLowerCase();
|
|
45
|
+
if (!res.ok || ct.indexOf('spreadsheet') === -1) {
|
|
46
|
+
var msg = 'Export failed';
|
|
47
|
+
try {
|
|
48
|
+
if (ct.indexOf('json') !== -1) {
|
|
49
|
+
var j = await res.json();
|
|
50
|
+
msg = j.error || msg;
|
|
51
|
+
} else {
|
|
52
|
+
var t = await res.text();
|
|
53
|
+
if (t) msg = t.slice(0, 300);
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {}
|
|
56
|
+
throw new Error(msg);
|
|
57
|
+
}
|
|
58
|
+
const blob = await res.blob();
|
|
59
|
+
var url = URL.createObjectURL(blob);
|
|
60
|
+
var a = document.createElement('a');
|
|
61
|
+
a.href = url;
|
|
62
|
+
a.download = modelName + '-export.xlsx';
|
|
63
|
+
a.click();
|
|
64
|
+
URL.revokeObjectURL(url);
|
|
65
|
+
}
|
|
66
|
+
|
|
35
67
|
// Helper: Capitalize first letter of each word
|
|
36
68
|
function capitalizeWords(str) {
|
|
37
69
|
if (!str) return '';
|
|
@@ -659,7 +691,7 @@ const FieldRenderers = {
|
|
|
659
691
|
|
|
660
692
|
return m('.mb-4', [
|
|
661
693
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
662
|
-
m('input.w-full.px-3.py-2.border.border-gray-300
|
|
694
|
+
m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
663
695
|
id: col.name,
|
|
664
696
|
name: col.name,
|
|
665
697
|
type: inputType,
|
|
@@ -671,7 +703,7 @@ const FieldRenderers = {
|
|
|
671
703
|
required: !col.nullable && !readonly,
|
|
672
704
|
readonly: readonly,
|
|
673
705
|
disabled: readonly,
|
|
674
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
706
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
675
707
|
oninput: (e) => onChange(e.target.value),
|
|
676
708
|
}),
|
|
677
709
|
hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
|
|
@@ -689,7 +721,7 @@ const FieldRenderers = {
|
|
|
689
721
|
|
|
690
722
|
return m('.mb-4', [
|
|
691
723
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
692
|
-
m('textarea.w-full.px-3.py-2.border.border-gray-300
|
|
724
|
+
m('textarea.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
693
725
|
id: col.name,
|
|
694
726
|
name: col.name,
|
|
695
727
|
rows: rows,
|
|
@@ -699,7 +731,7 @@ const FieldRenderers = {
|
|
|
699
731
|
required: !col.nullable && !readonly,
|
|
700
732
|
readonly: readonly,
|
|
701
733
|
disabled: readonly,
|
|
702
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
734
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
703
735
|
oninput: (e) => onChange(e.target.value),
|
|
704
736
|
}, value || ''),
|
|
705
737
|
hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
|
|
@@ -716,7 +748,7 @@ const FieldRenderers = {
|
|
|
716
748
|
|
|
717
749
|
return m('.mb-4', [
|
|
718
750
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
719
|
-
m('input.w-full.px-3.py-2.border.border-gray-300
|
|
751
|
+
m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
720
752
|
id: col.name,
|
|
721
753
|
name: col.name,
|
|
722
754
|
type: 'number',
|
|
@@ -728,7 +760,7 @@ const FieldRenderers = {
|
|
|
728
760
|
required: !col.nullable && !readonly,
|
|
729
761
|
readonly: readonly,
|
|
730
762
|
disabled: readonly,
|
|
731
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
763
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
732
764
|
oninput: (e) => onChange(e.target.value === '' ? null : parseInt(e.target.value, 10)),
|
|
733
765
|
}),
|
|
734
766
|
hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
|
|
@@ -745,7 +777,7 @@ const FieldRenderers = {
|
|
|
745
777
|
|
|
746
778
|
return m('.mb-4', [
|
|
747
779
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
748
|
-
m('input.w-full.px-3.py-2.border.border-gray-300
|
|
780
|
+
m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
749
781
|
id: col.name,
|
|
750
782
|
name: col.name,
|
|
751
783
|
type: 'number',
|
|
@@ -757,7 +789,7 @@ const FieldRenderers = {
|
|
|
757
789
|
required: !col.nullable && !readonly,
|
|
758
790
|
readonly: readonly,
|
|
759
791
|
disabled: readonly,
|
|
760
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
792
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
761
793
|
oninput: (e) => onChange(e.target.value === '' ? null : parseFloat(e.target.value)),
|
|
762
794
|
}),
|
|
763
795
|
hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
|
|
@@ -772,14 +804,14 @@ const FieldRenderers = {
|
|
|
772
804
|
|
|
773
805
|
return m('.mb-4', [
|
|
774
806
|
m('label.flex.items-center.cursor-pointer', { class: readonly ? 'cursor-not-allowed' : '' }, [
|
|
775
|
-
m('input.mr-2.w-4.h-4', {
|
|
807
|
+
m('input.mr-2.w-4.h-4.rounded.border-gray-300.dark:border-slate-600.text-indigo-600.focus:ring-indigo-500', {
|
|
776
808
|
type: 'checkbox',
|
|
777
809
|
name: col.name,
|
|
778
810
|
checked: Boolean(value),
|
|
779
811
|
disabled: readonly,
|
|
780
812
|
onchange: (e) => onChange(e.target.checked),
|
|
781
813
|
}),
|
|
782
|
-
m('span.text-sm.font-medium.text-gray-700', label),
|
|
814
|
+
m('span.text-sm.font-medium.text-gray-700.dark:text-slate-300', label),
|
|
783
815
|
]),
|
|
784
816
|
hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
|
|
785
817
|
]);
|
|
@@ -796,7 +828,7 @@ const FieldRenderers = {
|
|
|
796
828
|
|
|
797
829
|
return m('.mb-4', [
|
|
798
830
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
799
|
-
m('input.w-full.px-3.py-2.border.border-gray-300
|
|
831
|
+
m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
800
832
|
id: col.name,
|
|
801
833
|
name: col.name,
|
|
802
834
|
type: 'date',
|
|
@@ -807,7 +839,7 @@ const FieldRenderers = {
|
|
|
807
839
|
required: !col.nullable && !readonly,
|
|
808
840
|
readonly: readonly,
|
|
809
841
|
disabled: readonly,
|
|
810
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
842
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
811
843
|
oninput: (e) => onChange(e.target.value),
|
|
812
844
|
}),
|
|
813
845
|
hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
|
|
@@ -825,7 +857,7 @@ const FieldRenderers = {
|
|
|
825
857
|
|
|
826
858
|
return m('.mb-4', [
|
|
827
859
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
828
|
-
m('input.w-full.px-3.py-2.border.border-gray-300
|
|
860
|
+
m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
829
861
|
id: col.name,
|
|
830
862
|
name: col.name,
|
|
831
863
|
type: 'datetime-local',
|
|
@@ -836,7 +868,7 @@ const FieldRenderers = {
|
|
|
836
868
|
required: !col.nullable && !readonly,
|
|
837
869
|
readonly: readonly,
|
|
838
870
|
disabled: readonly,
|
|
839
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
871
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
840
872
|
oninput: (e) => onChange(e.target.value),
|
|
841
873
|
}),
|
|
842
874
|
hint ? m('p.text-xs.text-gray-500 dark:text-slate-400.mt-1', hint) : null,
|
|
@@ -852,13 +884,13 @@ const FieldRenderers = {
|
|
|
852
884
|
|
|
853
885
|
return m('.mb-4', [
|
|
854
886
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
855
|
-
m('select.w-full.px-3.py-2.border.border-gray-300
|
|
887
|
+
m('select.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
856
888
|
id: col.name,
|
|
857
889
|
name: col.name,
|
|
858
890
|
value: value || '',
|
|
859
891
|
required: !col.nullable && !readonly,
|
|
860
892
|
disabled: readonly,
|
|
861
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
893
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
862
894
|
onchange: (e) => onChange(e.target.value),
|
|
863
895
|
}, [
|
|
864
896
|
col.nullable ? m('option', { value: '' }, '-- Select --') : null,
|
|
@@ -879,7 +911,7 @@ const FieldRenderers = {
|
|
|
879
911
|
|
|
880
912
|
return m('.mb-4', [
|
|
881
913
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
882
|
-
m('textarea.w-full.px-3.py-2.border.border-gray-300
|
|
914
|
+
m('textarea.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500.font-mono.text-sm.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
883
915
|
id: col.name,
|
|
884
916
|
name: col.name,
|
|
885
917
|
rows: rows,
|
|
@@ -887,7 +919,7 @@ const FieldRenderers = {
|
|
|
887
919
|
required: !col.nullable && !readonly,
|
|
888
920
|
readonly: readonly,
|
|
889
921
|
disabled: readonly,
|
|
890
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
922
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
891
923
|
oninput: (e) => {
|
|
892
924
|
try {
|
|
893
925
|
const parsed = JSON.parse(e.target.value);
|
|
@@ -912,7 +944,7 @@ const FieldRenderers = {
|
|
|
912
944
|
|
|
913
945
|
return m('.mb-4', [
|
|
914
946
|
m('label.block.text-sm.font-medium.text-gray-700 dark:text-slate-300.mb-1', { for: col.name }, label),
|
|
915
|
-
m('input.w-full.px-3.py-2.border.border-gray-300
|
|
947
|
+
m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500.focus:outline-none.focus:ring-2.focus:ring-blue-500.dark:focus:ring-blue-400', {
|
|
916
948
|
id: col.name,
|
|
917
949
|
name: col.name,
|
|
918
950
|
type: 'text',
|
|
@@ -923,7 +955,7 @@ const FieldRenderers = {
|
|
|
923
955
|
required: !col.nullable && !readonly,
|
|
924
956
|
readonly: readonly,
|
|
925
957
|
disabled: readonly,
|
|
926
|
-
class: readonly ? 'bg-gray-100 cursor-not-allowed' : '',
|
|
958
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 cursor-not-allowed' : '',
|
|
927
959
|
oninput: (e) => {
|
|
928
960
|
const arr = e.target.value.split(',').map(s => s.trim()).filter(s => s);
|
|
929
961
|
onChange(arr);
|
|
@@ -1035,9 +1067,9 @@ const RichTextField = {
|
|
|
1035
1067
|
label,
|
|
1036
1068
|
required ? m('span.text-red-500', ' *') : null
|
|
1037
1069
|
),
|
|
1038
|
-
m('div.border.border-gray-300
|
|
1070
|
+
m('div.border.border-gray-300.dark:border-slate-600.rounded.bg-white.dark:bg-slate-900/50', {
|
|
1039
1071
|
id: editorId,
|
|
1040
|
-
class: readonly ? 'bg-gray-100 opacity-50' : '',
|
|
1072
|
+
class: readonly ? 'bg-gray-100 dark:bg-slate-800 opacity-50' : '',
|
|
1041
1073
|
style: 'min-height: 200px;'
|
|
1042
1074
|
}),
|
|
1043
1075
|
m('input[type=hidden]', {
|
|
@@ -1115,7 +1147,7 @@ const FileUploadField = {
|
|
|
1115
1147
|
if (readonly) {
|
|
1116
1148
|
return m('.mb-4', [
|
|
1117
1149
|
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-1', label, required ? m('span.text-red-500', ' *') : null),
|
|
1118
|
-
value ? m('a.text-indigo-600.break-all', { href: value, target: '_blank', rel: 'noopener noreferrer' }, value) : m('span.text-gray-400', '—'),
|
|
1150
|
+
value ? m('a.text-indigo-600.dark:text-indigo-400.break-all', { href: value, target: '_blank', rel: 'noopener noreferrer' }, value) : m('span.text-gray-400.dark:text-slate-500', '—'),
|
|
1119
1151
|
hint ? m('p.text-xs.text-gray-500.dark:text-slate-400.mt-1', hint) : null,
|
|
1120
1152
|
]);
|
|
1121
1153
|
}
|
|
@@ -1123,7 +1155,7 @@ const FileUploadField = {
|
|
|
1123
1155
|
return m('.mb-4', [
|
|
1124
1156
|
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-1', { for: col.name }, label, required ? m('span.text-red-500', ' *') : null),
|
|
1125
1157
|
m('p.text-xs.text-amber-700.dark:text-amber-400.mb-2', 'Upload URL is not configured. Enter a public URL or path manually.'),
|
|
1126
|
-
m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded.bg-white.dark:bg-slate-
|
|
1158
|
+
m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-md.bg-white.dark:bg-slate-900/70.text-gray-900.dark:text-slate-100.placeholder-gray-400.dark:placeholder-slate-500', {
|
|
1127
1159
|
type: 'text',
|
|
1128
1160
|
id: col.name,
|
|
1129
1161
|
name: col.name,
|
|
@@ -1137,7 +1169,7 @@ const FileUploadField = {
|
|
|
1137
1169
|
}
|
|
1138
1170
|
return m('.mb-4', [
|
|
1139
1171
|
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-1', label, required ? m('span.text-red-500', ' *') : null),
|
|
1140
|
-
m('div#' + dropZoneId + '.border-2.border-dashed.border-gray-300.dark:border-slate-600.rounded.p-8.text-center', { style: 'cursor: pointer;' }, [
|
|
1172
|
+
m('div#' + dropZoneId + '.border-2.border-dashed.border-gray-300.dark:border-slate-600.rounded-lg.p-8.text-center.bg-gray-50.dark:bg-slate-900/40', { style: 'cursor: pointer;' }, [
|
|
1141
1173
|
m('input[type=file].hidden', {
|
|
1142
1174
|
id: 'file-input-' + col.name,
|
|
1143
1175
|
accept: accept,
|
|
@@ -1153,7 +1185,7 @@ const FileUploadField = {
|
|
|
1153
1185
|
]),
|
|
1154
1186
|
value ? m('.mt-4.text-left', [
|
|
1155
1187
|
m('p.text-sm.text-gray-600.dark:text-slate-400.break-all', 'Current: ' + value),
|
|
1156
|
-
m('button.text-red-600.hover:text-red-800.text-sm.mt-2', {
|
|
1188
|
+
m('button.text-red-600.dark:text-red-400.hover:text-red-800.dark:hover:text-red-300.text-sm.mt-2', {
|
|
1157
1189
|
type: 'button',
|
|
1158
1190
|
onclick: function () { if (onChange) onChange(''); },
|
|
1159
1191
|
}, 'Remove'),
|
|
@@ -1285,8 +1317,8 @@ const LoginForm = {
|
|
|
1285
1317
|
m('.w-full.max-w-md', [
|
|
1286
1318
|
m('.bg-white dark:bg-slate-800.rounded-2xl.shadow-2xl.p-6.sm:p-8', [
|
|
1287
1319
|
m('div.text-center.mb-6', [
|
|
1288
|
-
m('h1.text-2xl.sm:text-3xl.font-bold.text-gray-900', 'Admin Login'),
|
|
1289
|
-
m('p.text-gray-500
|
|
1320
|
+
m('h1.text-2xl.sm:text-3xl.font-bold.text-gray-900.dark:text-slate-50', 'Admin Login'),
|
|
1321
|
+
m('p.text-gray-500.dark:text-slate-400.text-sm.mt-1', 'Sign in to your account'),
|
|
1290
1322
|
]),
|
|
1291
1323
|
m('form', {
|
|
1292
1324
|
onsubmit: async (e) => {
|
|
@@ -1308,10 +1340,10 @@ const LoginForm = {
|
|
|
1308
1340
|
}
|
|
1309
1341
|
}
|
|
1310
1342
|
}, [
|
|
1311
|
-
state.error ? m('.bg-red-50.border.border-red-200.text-red-700.px-4.py-3.rounded-lg.mb-4.text-sm', state.error) : null,
|
|
1343
|
+
state.error ? m('.bg-red-50.border.border-red-200.text-red-700.dark:bg-red-950/50.dark:border-red-800/80.dark:text-red-200.px-4.py-3.rounded-lg.mb-4.text-sm', state.error) : null,
|
|
1312
1344
|
m('.mb-4', [
|
|
1313
|
-
m('label.block.text-sm.font-medium.text-gray-700
|
|
1314
|
-
m('input#email.w-full.px-3.py-2.5.border.border-gray-300
|
|
1345
|
+
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-2', { for: 'email' }, 'Email'),
|
|
1346
|
+
m('input#email.w-full.px-3.py-2.5.bg-white.text-gray-900.border.border-gray-300.rounded-lg.placeholder-gray-400.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors.dark:bg-slate-900/80.dark:border-slate-500.dark:text-slate-100.dark:placeholder-slate-500', {
|
|
1315
1347
|
type: 'email',
|
|
1316
1348
|
name: 'email',
|
|
1317
1349
|
required: true,
|
|
@@ -1319,14 +1351,14 @@ const LoginForm = {
|
|
|
1319
1351
|
}),
|
|
1320
1352
|
]),
|
|
1321
1353
|
m('.mb-6', [
|
|
1322
|
-
m('label.block.text-sm.font-medium.text-gray-700
|
|
1323
|
-
m('input#password.w-full.px-3.py-2.5.border.border-gray-300
|
|
1354
|
+
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-2', { for: 'password' }, 'Password'),
|
|
1355
|
+
m('input#password.w-full.px-3.py-2.5.bg-white.text-gray-900.border.border-gray-300.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors.dark:bg-slate-900/80.dark:border-slate-500.dark:text-slate-100', {
|
|
1324
1356
|
type: 'password',
|
|
1325
1357
|
name: 'password',
|
|
1326
1358
|
required: true,
|
|
1327
1359
|
}),
|
|
1328
1360
|
]),
|
|
1329
|
-
m('button.w-full.bg-blue-600.text-white.py-2.5.px-4.rounded-lg.font-medium.hover:bg-blue-700.focus:ring-2.focus:ring-blue-500.focus:ring-offset-2.disabled:opacity-50.transition-colors', {
|
|
1361
|
+
m('button.w-full.bg-blue-600.text-white.py-2.5.px-4.rounded-lg.font-medium.hover:bg-blue-700.focus:ring-2.focus:ring-blue-500.focus:ring-offset-2.dark:focus:ring-offset-slate-800.disabled:opacity-50.transition-colors', {
|
|
1330
1362
|
type: 'submit',
|
|
1331
1363
|
disabled: state.loading,
|
|
1332
1364
|
}, state.loading ? 'Logging in...' : 'Sign in'),
|
|
@@ -1342,8 +1374,8 @@ const SetupForm = {
|
|
|
1342
1374
|
m('.w-full.max-w-md', [
|
|
1343
1375
|
m('.bg-white dark:bg-slate-800.rounded-2xl.shadow-2xl.p-6.sm:p-8', [
|
|
1344
1376
|
m('div.text-center.mb-6', [
|
|
1345
|
-
m('h1.text-2xl.sm:text-3xl.font-bold.text-gray-900', 'Setup Admin Account'),
|
|
1346
|
-
m('p.text-gray-500
|
|
1377
|
+
m('h1.text-2xl.sm:text-3xl.font-bold.text-gray-900.dark:text-slate-50', 'Setup Admin Account'),
|
|
1378
|
+
m('p.text-gray-500.dark:text-slate-400.text-sm.mt-1', 'Create the first admin user account.'),
|
|
1347
1379
|
]),
|
|
1348
1380
|
m('form', {
|
|
1349
1381
|
onsubmit: async (e) => {
|
|
@@ -1367,18 +1399,18 @@ const SetupForm = {
|
|
|
1367
1399
|
}
|
|
1368
1400
|
}
|
|
1369
1401
|
}, [
|
|
1370
|
-
state.error ? m('.bg-red-50.border.border-red-200.text-red-700.px-4.py-3.rounded-lg.mb-4.text-sm', state.error) : null,
|
|
1402
|
+
state.error ? m('.bg-red-50.border.border-red-200.text-red-700.dark:bg-red-950/50.dark:border-red-800/80.dark:text-red-200.px-4.py-3.rounded-lg.mb-4.text-sm', state.error) : null,
|
|
1371
1403
|
m('.mb-4', [
|
|
1372
|
-
m('label.block.text-sm.font-medium.text-gray-700
|
|
1373
|
-
m('input#name.w-full.px-3.py-2.5.border.border-gray-300
|
|
1404
|
+
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-2', { for: 'name' }, 'Name'),
|
|
1405
|
+
m('input#name.w-full.px-3.py-2.5.bg-white.text-gray-900.border.border-gray-300.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors.dark:bg-slate-900/80.dark:border-slate-500.dark:text-slate-100', {
|
|
1374
1406
|
type: 'text',
|
|
1375
1407
|
name: 'name',
|
|
1376
1408
|
required: true,
|
|
1377
1409
|
}),
|
|
1378
1410
|
]),
|
|
1379
1411
|
m('.mb-4', [
|
|
1380
|
-
m('label.block.text-sm.font-medium.text-gray-700
|
|
1381
|
-
m('input#email.w-full.px-3.py-2.5.border.border-gray-300
|
|
1412
|
+
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-2', { for: 'email' }, 'Email'),
|
|
1413
|
+
m('input#email.w-full.px-3.py-2.5.bg-white.text-gray-900.border.border-gray-300.rounded-lg.placeholder-gray-400.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors.dark:bg-slate-900/80.dark:border-slate-500.dark:text-slate-100.dark:placeholder-slate-500', {
|
|
1382
1414
|
type: 'email',
|
|
1383
1415
|
name: 'email',
|
|
1384
1416
|
required: true,
|
|
@@ -1386,14 +1418,14 @@ const SetupForm = {
|
|
|
1386
1418
|
}),
|
|
1387
1419
|
]),
|
|
1388
1420
|
m('.mb-6', [
|
|
1389
|
-
m('label.block.text-sm.font-medium.text-gray-700
|
|
1390
|
-
m('input#password.w-full.px-3.py-2.5.border.border-gray-300
|
|
1421
|
+
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-2', { for: 'password' }, 'Password'),
|
|
1422
|
+
m('input#password.w-full.px-3.py-2.5.bg-white.text-gray-900.border.border-gray-300.rounded-lg.focus:ring-2.focus:ring-blue-500.focus:border-blue-500.transition-colors.dark:bg-slate-900/80.dark:border-slate-500.dark:text-slate-100', {
|
|
1391
1423
|
type: 'password',
|
|
1392
1424
|
name: 'password',
|
|
1393
1425
|
required: true,
|
|
1394
1426
|
}),
|
|
1395
1427
|
]),
|
|
1396
|
-
m('button.w-full.bg-blue-600.text-white.py-2.5.px-4.rounded-lg.font-medium.hover:bg-blue-700.focus:ring-2.focus:ring-blue-500.focus:ring-offset-2.disabled:opacity-50.transition-colors', {
|
|
1428
|
+
m('button.w-full.bg-blue-600.text-white.py-2.5.px-4.rounded-lg.font-medium.hover:bg-blue-700.focus:ring-2.focus:ring-blue-500.focus:ring-offset-2.dark:focus:ring-offset-slate-800.disabled:opacity-50.transition-colors', {
|
|
1397
1429
|
type: 'submit',
|
|
1398
1430
|
disabled: state.loading,
|
|
1399
1431
|
}, state.loading ? 'Creating...' : 'Create Admin Account'),
|
|
@@ -1985,29 +2017,13 @@ const RecordList = {
|
|
|
1985
2017
|
}, 'Trash'),
|
|
1986
2018
|
]) : null,
|
|
1987
2019
|
]),
|
|
1988
|
-
|
|
2020
|
+
m('.flex.flex-wrap.items-center.gap-2', [
|
|
1989
2021
|
m('button.inline-flex.items-center.gap-2.px-4.py-2.text-sm.font-medium.text-indigo-700.bg-white.dark:bg-slate-800.border.border-indigo-200.rounded-lg.hover:bg-indigo-50.transition-colors', {
|
|
1990
2022
|
onclick: async () => {
|
|
1991
|
-
const adminPath = window.__ADMIN_PATH__ || '/_admin';
|
|
1992
2023
|
try {
|
|
1993
2024
|
const payload = { selectAll: true, filters: state.filters };
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
credentials: 'include',
|
|
1997
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1998
|
-
body: JSON.stringify(payload),
|
|
1999
|
-
});
|
|
2000
|
-
if (!res.ok) {
|
|
2001
|
-
const err = await res.json().catch(function () { return {}; });
|
|
2002
|
-
throw new Error(err.error || 'Export failed');
|
|
2003
|
-
}
|
|
2004
|
-
const blob = await res.blob();
|
|
2005
|
-
const url = URL.createObjectURL(blob);
|
|
2006
|
-
const a = document.createElement('a');
|
|
2007
|
-
a.href = url;
|
|
2008
|
-
a.download = modelName + '-export.xlsx';
|
|
2009
|
-
a.click();
|
|
2010
|
-
URL.revokeObjectURL(url);
|
|
2025
|
+
if (state.trashedView) payload.trashed = 'only';
|
|
2026
|
+
await downloadDataExchangeXlsx(modelName, payload);
|
|
2011
2027
|
} catch (err) {
|
|
2012
2028
|
alert('Error: ' + err.message);
|
|
2013
2029
|
}
|
|
@@ -2018,7 +2034,7 @@ const RecordList = {
|
|
|
2018
2034
|
),
|
|
2019
2035
|
'Export Excel',
|
|
2020
2036
|
]),
|
|
2021
|
-
m('input[type=file]', {
|
|
2037
|
+
!state.trashedView ? m('input[type=file]', {
|
|
2022
2038
|
id: 'data-exchange-import-' + modelName,
|
|
2023
2039
|
style: 'display:none',
|
|
2024
2040
|
accept: '.csv,.xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv',
|
|
@@ -2043,7 +2059,7 @@ const RecordList = {
|
|
|
2043
2059
|
}
|
|
2044
2060
|
var msg = 'Import finished: created ' + body.created + ', updated ' + (body.updated || 0) + ', failed ' + (body.failed || 0);
|
|
2045
2061
|
if (body.errors && body.errors.length) {
|
|
2046
|
-
msg += '
|
|
2062
|
+
msg += 'First errors: ' + body.errors.slice(0, 3).map(function (x) { return 'row ' + x.row + ': ' + x.message; }).join('; ');
|
|
2047
2063
|
}
|
|
2048
2064
|
alert(msg);
|
|
2049
2065
|
loadRecords(modelName, state.pagination.page, state.filters);
|
|
@@ -2051,8 +2067,8 @@ const RecordList = {
|
|
|
2051
2067
|
alert('Error: ' + err.message);
|
|
2052
2068
|
}
|
|
2053
2069
|
},
|
|
2054
|
-
}),
|
|
2055
|
-
m('button.inline-flex.items-center.gap-2.px-4.py-2.text-sm.font-medium.text-indigo-700.bg-white.dark:bg-slate-800.border.border-indigo-200.rounded-lg.hover:bg-indigo-50.transition-colors', {
|
|
2070
|
+
}) : null,
|
|
2071
|
+
!state.trashedView ? m('button.inline-flex.items-center.gap-2.px-4.py-2.text-sm.font-medium.text-indigo-700.bg-white.dark:bg-slate-800.border.border-indigo-200.rounded-lg.hover:bg-indigo-50.transition-colors', {
|
|
2056
2072
|
onclick: function () {
|
|
2057
2073
|
var el = document.getElementById('data-exchange-import-' + modelName);
|
|
2058
2074
|
if (el) el.click();
|
|
@@ -2062,8 +2078,8 @@ const RecordList = {
|
|
|
2062
2078
|
m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1M7 10l5 5m0 0l5-5m-5 5V4' })
|
|
2063
2079
|
),
|
|
2064
2080
|
'Import',
|
|
2065
|
-
]),
|
|
2066
|
-
m('button.inline-flex.items-center.gap-2.px-4.py-2.text-sm.font-medium.text-white.bg-indigo-600.rounded-lg.hover:bg-indigo-700.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
|
|
2081
|
+
]) : null,
|
|
2082
|
+
!state.trashedView ? m('button.inline-flex.items-center.gap-2.px-4.py-2.text-sm.font-medium.text-white.bg-indigo-600.rounded-lg.hover:bg-indigo-700.focus:outline-none.focus:ring-2.focus:ring-indigo-500', {
|
|
2067
2083
|
onclick: () => {
|
|
2068
2084
|
state.currentRecord = null;
|
|
2069
2085
|
state.editing = true;
|
|
@@ -2074,8 +2090,8 @@ const RecordList = {
|
|
|
2074
2090
|
m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M12 4v16m8-8H4' }),
|
|
2075
2091
|
]),
|
|
2076
2092
|
'New Record',
|
|
2077
|
-
]),
|
|
2078
|
-
])
|
|
2093
|
+
]) : null,
|
|
2094
|
+
]),
|
|
2079
2095
|
]),
|
|
2080
2096
|
|
|
2081
2097
|
// Quick Filters Bar
|
|
@@ -2281,33 +2297,17 @@ const RecordList = {
|
|
|
2281
2297
|
),
|
|
2282
2298
|
'Export CSV',
|
|
2283
2299
|
]) : null,
|
|
2284
|
-
|
|
2300
|
+
m('button.inline-flex.items-center.gap-1.px-3.py-1.5.text-sm.font-medium.text-violet-600.bg-white dark:bg-slate-800.border.border-violet-200.rounded.hover:bg-violet-50.transition-colors', {
|
|
2285
2301
|
disabled: state.bulkActionInProgress,
|
|
2286
2302
|
onclick: async () => {
|
|
2287
2303
|
state.bulkActionInProgress = true;
|
|
2288
2304
|
m.redraw();
|
|
2289
2305
|
try {
|
|
2290
|
-
const adminPath = window.__ADMIN_PATH__ || '/_admin';
|
|
2291
2306
|
const payload = state.selectAllMode
|
|
2292
2307
|
? { selectAll: true, filters: state.filters }
|
|
2293
2308
|
: { ids: Array.from(state.selectedRecords) };
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
credentials: 'include',
|
|
2297
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2298
|
-
body: JSON.stringify(payload),
|
|
2299
|
-
});
|
|
2300
|
-
if (!res.ok) {
|
|
2301
|
-
const err = await res.json().catch(function () { return {}; });
|
|
2302
|
-
throw new Error(err.error || 'Export failed');
|
|
2303
|
-
}
|
|
2304
|
-
const blob = await res.blob();
|
|
2305
|
-
const url = URL.createObjectURL(blob);
|
|
2306
|
-
const a = document.createElement('a');
|
|
2307
|
-
a.href = url;
|
|
2308
|
-
a.download = modelName + '-export.xlsx';
|
|
2309
|
-
a.click();
|
|
2310
|
-
URL.revokeObjectURL(url);
|
|
2309
|
+
if (state.trashedView) payload.trashed = 'only';
|
|
2310
|
+
await downloadDataExchangeXlsx(modelName, payload);
|
|
2311
2311
|
} catch (err) {
|
|
2312
2312
|
alert('Error: ' + err.message);
|
|
2313
2313
|
} finally {
|
|
@@ -2320,7 +2320,7 @@ const RecordList = {
|
|
|
2320
2320
|
m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4' })
|
|
2321
2321
|
),
|
|
2322
2322
|
'Export Excel',
|
|
2323
|
-
])
|
|
2323
|
+
]),
|
|
2324
2324
|
!state.trashedView ? m(BulkFieldUpdateDropdown, {
|
|
2325
2325
|
modelName: modelName,
|
|
2326
2326
|
selectedIds: state.selectAllMode ? null : Array.from(state.selectedRecords),
|
|
@@ -2350,7 +2350,7 @@ const RecordList = {
|
|
|
2350
2350
|
m('thead.bg-gray-50.dark:bg-slate-900', { style: 'position: sticky; top: 0; z-index: 20;' }, [
|
|
2351
2351
|
m('tr', [
|
|
2352
2352
|
// Checkbox column header (sticky left, box-shadow on right)
|
|
2353
|
-
m('th.px-4.py-3.text-left.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200', { style: 'width: 40px; position: sticky; left: 0; z-index: 15; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);' }, [
|
|
2353
|
+
m('th.px-4.py-3.text-left.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200 dark:border-slate-700', { style: 'width: 40px; position: sticky; left: 0; z-index: 15; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);' }, [
|
|
2354
2354
|
m('input[type=checkbox].rounded.border-gray-300 dark:border-slate-600.text-indigo-600.focus:ring-indigo-500', {
|
|
2355
2355
|
checked: state.records.length > 0 && state.selectedRecords && state.selectedRecords.size === state.records.length,
|
|
2356
2356
|
indeterminate: state.selectedRecords && state.selectedRecords.size > 0 && state.selectedRecords.size < state.records.length,
|
|
@@ -2366,23 +2366,23 @@ const RecordList = {
|
|
|
2366
2366
|
]),
|
|
2367
2367
|
// Dynamic column headers (first column sticky left with box-shadow)
|
|
2368
2368
|
...displayColumns.map((col, i) =>
|
|
2369
|
-
m('th.px-4.py-3.text-left.text-xs.font-medium.text-gray-500 dark:text-slate-400.uppercase.tracking-wider.whitespace-nowrap.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200',
|
|
2369
|
+
m('th.px-4.py-3.text-left.text-xs.font-medium.text-gray-500 dark:text-slate-400.uppercase.tracking-wider.whitespace-nowrap.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200 dark:border-slate-700',
|
|
2370
2370
|
i === 0 ? { style: 'position: sticky; left: 40px; z-index: 15; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);' } : {},
|
|
2371
2371
|
formatColumnLabel(col.name)
|
|
2372
2372
|
)
|
|
2373
2373
|
),
|
|
2374
2374
|
// Sticky actions header (sticky right, box-shadow on left)
|
|
2375
|
-
m('th.px-4.py-3.text-right.text-xs.font-medium.text-gray-500 dark:text-slate-400.uppercase.tracking-wider.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200', {
|
|
2375
|
+
m('th.px-4.py-3.text-right.text-xs.font-medium.text-gray-500 dark:text-slate-400.uppercase.tracking-wider.bg-gray-50 dark:bg-slate-900.border-b.border-gray-200 dark:border-slate-700', {
|
|
2376
2376
|
style: 'position: sticky; right: 0; min-width: 120px; z-index: 15; box-shadow: -4px 0 8px -4px rgba(0,0,0,0.08);',
|
|
2377
2377
|
}, 'Actions'),
|
|
2378
2378
|
]),
|
|
2379
2379
|
]),
|
|
2380
|
-
m('tbody.divide-y.divide-gray-100', state.records.map(record =>
|
|
2381
|
-
m('tr.hover:bg-gray-50 dark:hover:bg-slate-800/50
|
|
2382
|
-
class: state.selectedRecords && state.selectedRecords.has(record[primaryKey]) ? 'bg-indigo-50' : '',
|
|
2380
|
+
m('tbody.divide-y.divide-gray-100.dark:divide-slate-700', state.records.map(record =>
|
|
2381
|
+
m('tr.hover:bg-gray-50 dark:hover:bg-slate-800/50.transition-colors', {
|
|
2382
|
+
class: state.selectedRecords && state.selectedRecords.has(record[primaryKey]) ? 'bg-indigo-50 dark:bg-indigo-950/50' : '',
|
|
2383
2383
|
}, [
|
|
2384
2384
|
// Checkbox cell (sticky left, box-shadow on right)
|
|
2385
|
-
m('td.px-4.py-3.bg-white', {
|
|
2385
|
+
m('td.px-4.py-3.bg-white.dark:bg-slate-800', {
|
|
2386
2386
|
style: 'position: sticky; left: 0; z-index: 5; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);',
|
|
2387
2387
|
}, [
|
|
2388
2388
|
m('input[type=checkbox].rounded.border-gray-300 dark:border-slate-600.text-indigo-600.focus:ring-indigo-500', {
|
|
@@ -2400,17 +2400,17 @@ const RecordList = {
|
|
|
2400
2400
|
]),
|
|
2401
2401
|
// Dynamic cell values (first column sticky left with box-shadow)
|
|
2402
2402
|
...displayColumns.map((col, i) =>
|
|
2403
|
-
m('td.px-4.py-3.text-sm.whitespace-nowrap.text-gray-700 dark:text-slate-300.bg-white',
|
|
2403
|
+
m('td.px-4.py-3.text-sm.whitespace-nowrap.text-gray-700 dark:text-slate-300.bg-white dark:bg-slate-800',
|
|
2404
2404
|
i === 0 ? { style: 'position: sticky; left: 40px; z-index: 5; box-shadow: 4px 0 8px -4px rgba(0,0,0,0.08);' } : {},
|
|
2405
2405
|
formatCellValue(record[col.name], col)
|
|
2406
2406
|
)
|
|
2407
2407
|
),
|
|
2408
2408
|
// Sticky actions cell (sticky right, box-shadow on left)
|
|
2409
|
-
m('td.px-4.py-3.text-sm.text-right.whitespace-nowrap.bg-white', {
|
|
2409
|
+
m('td.px-4.py-3.text-sm.text-right.whitespace-nowrap.text-gray-700 dark:text-slate-300.bg-white dark:bg-slate-800', {
|
|
2410
2410
|
style: 'position: sticky; right: 0; z-index: 5; box-shadow: -4px 0 8px -4px rgba(0,0,0,0.08);',
|
|
2411
2411
|
}, [
|
|
2412
2412
|
state.trashedView && modelMeta?.softDelete
|
|
2413
|
-
? m('button.inline-flex.items-center.px-2.py-1.text-sm.text-green-600.hover:text-green-800.hover:bg-green-50.rounded.transition-colors', {
|
|
2413
|
+
? m('button.inline-flex.items-center.px-2.py-1.text-sm.text-green-600.dark:text-green-400.hover:text-green-800.dark:hover:text-green-300.hover:bg-green-50.dark:hover:bg-green-950/40.rounded.transition-colors', {
|
|
2414
2414
|
onclick: async () => {
|
|
2415
2415
|
try {
|
|
2416
2416
|
await api.post('/models/' + modelName + '/records/' + record[primaryKey] + '/restore');
|
|
@@ -2421,14 +2421,14 @@ const RecordList = {
|
|
|
2421
2421
|
},
|
|
2422
2422
|
}, 'Restore')
|
|
2423
2423
|
: [
|
|
2424
|
-
m('button.inline-flex.items-center.px-2.py-1.text-sm.text-indigo-600.hover:text-indigo-800.hover:bg-indigo-50.rounded.mr-1.transition-colors', {
|
|
2424
|
+
m('button.inline-flex.items-center.px-2.py-1.text-sm.text-indigo-600.dark:text-indigo-400.hover:text-indigo-800.dark:hover:text-indigo-300.hover:bg-indigo-50.dark:hover:bg-indigo-950/40.rounded.mr-1.transition-colors', {
|
|
2425
2425
|
onclick: () => {
|
|
2426
2426
|
state.currentRecord = record;
|
|
2427
2427
|
state.editing = true;
|
|
2428
2428
|
m.route.set('/models/' + modelName + '/edit/' + record[primaryKey]);
|
|
2429
2429
|
},
|
|
2430
2430
|
}, 'Edit'),
|
|
2431
|
-
m('button.inline-flex.items-center.px-2.py-1.text-sm.text-red-600.hover:text-red-800.hover:bg-red-50.rounded.transition-colors', {
|
|
2431
|
+
m('button.inline-flex.items-center.px-2.py-1.text-sm.text-red-600.dark:text-red-400.hover:text-red-800.dark:hover:text-red-300.hover:bg-red-50.dark:hover:bg-red-950/40.rounded.transition-colors', {
|
|
2432
2432
|
onclick: async () => {
|
|
2433
2433
|
if (confirm('Are you sure you want to delete this record?')) {
|
|
2434
2434
|
try {
|
|
@@ -2516,14 +2516,14 @@ const RecordForm = {
|
|
|
2516
2516
|
|
|
2517
2517
|
return m(Layout, { breadcrumbs }, [
|
|
2518
2518
|
m('.flex.items-center.justify-between.mb-6', [
|
|
2519
|
-
m('h2.text-2xl.font-bold', isNew ? 'New Record' : 'Edit Record'),
|
|
2520
|
-
modelMeta ? m('span.text-gray-500', modelMeta.label || modelMeta.name) : null,
|
|
2519
|
+
m('h2.text-2xl.font-bold.text-gray-900.dark:text-slate-100', isNew ? 'New Record' : 'Edit Record'),
|
|
2520
|
+
modelMeta ? m('span.text-gray-500.dark:text-slate-400', modelMeta.label || modelMeta.name) : null,
|
|
2521
2521
|
]),
|
|
2522
2522
|
|
|
2523
|
-
state.loading ? m('p.text-gray-600', 'Loading...') :
|
|
2524
|
-
state.error && !modelMeta ? m('.bg-red-
|
|
2523
|
+
state.loading ? m('p.text-gray-600.dark:text-slate-400', 'Loading...') :
|
|
2524
|
+
state.error && !modelMeta ? m('.bg-red-50.dark:bg-red-950/40.border.border-red-200.dark:border-red-800.text-red-800.dark:text-red-200.px-4.py-3.rounded-lg', state.error) :
|
|
2525
2525
|
|
|
2526
|
-
m('form.bg-white
|
|
2526
|
+
m('form.bg-white.dark:bg-slate-800.rounded-lg.shadow-sm.border.border-gray-200.dark:border-slate-700.flex.flex-col', {
|
|
2527
2527
|
style: 'min-height: calc(100vh - 280px);',
|
|
2528
2528
|
onsubmit: async (e) => {
|
|
2529
2529
|
e.preventDefault();
|
|
@@ -2592,7 +2592,7 @@ const RecordForm = {
|
|
|
2592
2592
|
}, [
|
|
2593
2593
|
// Form content (scrollable)
|
|
2594
2594
|
m('.p-6.flex-1.overflow-y-auto', [
|
|
2595
|
-
state.error ? m('.bg-red-
|
|
2595
|
+
state.error ? m('.bg-red-50.dark:bg-red-950/40.border.border-red-200.dark:border-red-800.text-red-800.dark:text-red-200.px-4.py-3.rounded-lg.mb-4', state.error) : null,
|
|
2596
2596
|
|
|
2597
2597
|
// Render form fields based on model columns
|
|
2598
2598
|
modelMeta && modelMeta.columns ? modelMeta.columns.map(col => {
|
|
@@ -2616,8 +2616,8 @@ const RecordForm = {
|
|
|
2616
2616
|
]),
|
|
2617
2617
|
|
|
2618
2618
|
// Sticky footer buttons
|
|
2619
|
-
m('.flex.gap-4.p-4.border-t.bg-gray-50
|
|
2620
|
-
m('button.bg-blue-600.text-white.px-6.py-2.rounded.hover:bg-blue-700.disabled:opacity-50', {
|
|
2619
|
+
m('.flex.gap-4.p-4.border-t.border-gray-200.dark:border-slate-700.bg-gray-50.dark:bg-slate-900.sticky.bottom-0', [
|
|
2620
|
+
m('button.bg-blue-600.dark:bg-blue-500.text-white.px-6.py-2.rounded-lg.hover:bg-blue-700.dark:hover:bg-blue-600.disabled:opacity-50', {
|
|
2621
2621
|
type: 'submit',
|
|
2622
2622
|
disabled: state.loading,
|
|
2623
2623
|
}, state.loading ? 'Saving...' : 'Save'),
|