waibu-db 1.1.4 → 1.1.6

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.
@@ -1,4 +1,33 @@
1
1
  {
2
+ "pageOfPages%s%s": "Page %s of %s pages",
3
+ "recsFound%s": "%s record(s) found",
4
+ "recsPerPage": "recs per page",
5
+ "addAsNewClone": "Add as New Clone",
6
+ "details": "Details",
7
+ "clone": "Clone",
8
+ "delivery": "Delivery",
9
+ "saveAsFile": "Save as File",
10
+ "formattedField": "Formatted Field",
11
+ "formattedValue": "Formatted Value",
12
+ "zipped": "Zipped",
13
+ "fileType": "File Type",
14
+ "aboutToRemoveRecord": "You're about to remove one or more records. Are you really sure to do this?",
15
+ "columns": "Kolom",
16
+ "noStatYet": "Maaf, belum ada statistik",
17
+ "%s - %s": "%s - %s",
18
+ "list": "List ",
19
+ "add": "Add ",
20
+ "edit": "Edit",
21
+ "yes": "Yes",
22
+ "no": "No",
23
+ "db": "DB",
24
+ "excelXlsx": "Excel XLSX",
25
+ "csv": "CSV",
26
+ "xml": "XML",
27
+ "json": "JSON",
28
+ "addMoreAfterSubmit": "Add more after submit",
29
+ "clonePrevious": "Clone previous",
30
+ "actionPermanentlyDisabled%s": "Sorry, action '%s' is permanently disabled at database level. For more information, please contact your Admin immediately, thank you!",
2
31
  "op": {
3
32
  "equals": "Equals",
4
33
  "notEquals": "Not Equals",
package/bajo/intl/id.json CHANGED
@@ -1,26 +1,33 @@
1
1
  {
2
- "Page %s of %s pages": "Hal %s dari %s",
3
- "%s record(s) found": "Ditemukan %s data",
4
- "recs per page": "data per halaman",
5
- "Add as New Clone": "Tambah sbg Duplikat",
6
- "Details": "Detil",
7
- "Clone": "Gandakan",
8
- "Delivery": "Penyampaian",
9
- "Save as File": "Simpan sbg Berkas",
10
- "Formatted Field": "Kolom Terformat",
11
- "Formatted Value": "Nilai Terformat",
12
- "Zipped": "Di kompres",
13
- "File Type": "Tipe Berkas",
14
- "You're about to remove one or more records. Are you really sure to do this?": "Anda akan menghapus satu atau lebih data. Anda yakin akan melakukannya?",
15
- "Columns": "Kolom",
16
- "No statistic yet, sorry": "Maaf, belum ada statistik",
2
+ "pageOfPages%s%s": "Hal %s dari %s",
3
+ "recsFound%s": "Ditemukan %s data",
4
+ "recsPerPage": "data per halaman",
5
+ "addAsNewClone": "Tambah sbg Duplikat",
6
+ "details": "Detil",
7
+ "clone": "Gandakan",
8
+ "delivery": "Penyampaian",
9
+ "saveAsFile": "Simpan sbg Berkas",
10
+ "formattedField": "Kolom Terformat",
11
+ "formattedValue": "Nilai Terformat",
12
+ "zipped": "Di kompres",
13
+ "fileType": "Tipe Berkas",
14
+ "aboutToRemoveRecord": "Anda akan menghapus satu atau lebih data. Anda yakin akan melakukannya?",
15
+ "columns": "Kolom",
16
+ "noStatYet": "Maaf, belum ada statistik",
17
17
  "%s - %s": "%s - %s",
18
- "List": "Daftar",
19
- "Add": "Tambah",
20
- "Edit": "Ubah",
21
- "Yes": "Ya",
22
- "No": "Tidak",
23
- "Db": "DB",
18
+ "list": "Daftar",
19
+ "add": "Tambah",
20
+ "edit": "Ubah",
21
+ "yes": "Ya",
22
+ "no": "Tidak",
23
+ "db": "DB",
24
+ "excelXlsx": "Excel XLSX",
25
+ "csv": "CSV",
26
+ "xml": "XML",
27
+ "json": "JSON",
28
+ "addMoreAfterSubmit": "Tambah lagi setelah kirim",
29
+ "clonePrevious": "Duplikasi sebelumnya",
30
+ "actionPermanentlyDisabled%s": "Sorry, action '%s' is permanently disabled at database level. For more information, please contact your Admin immediately, thank you!",
24
31
  "op": {
25
32
  "equals": "Sama Dengan",
26
33
  "notEquals": "Tidak Sama Dengan",
@@ -1,4 +1,4 @@
1
- <c:grid-row gutter="3" margin="bottom-4">
1
+ <c:grid-row gutter="3">
2
2
  <c:grid-col col="6-lg">
3
3
  <c:wdb-recs-info pages recs-per-page recs-per-page-values="10 15 25 50" />
4
4
  </c:grid-col>
@@ -6,13 +6,13 @@
6
6
  </c:grid-col>
7
7
  <c:grid-col col="6-md">
8
8
  <c:div flex="justify-content:end-md align-items:center">
9
- <c:btn type="reset" color="secondary" t:content="Reset" />
10
- <c:btn type="submit" color="primary" t:content="Submit" margin="start-2"/>
9
+ <c:btn type="reset" color="secondary" t:content="reset" />
10
+ <c:btn type="submit" color="primary" t:content="submit" margin="start-2"/>
11
11
  </c:div>
12
12
  </c:grid-col>
13
13
  <c:grid-col col="12" margin="top-4">
14
- <c:form-switch name="_addmore" t:label="Add more after submit"/>
15
- <c:form-switch name="_cloneprev" t:label="Clone previous"/>
14
+ <c:form-switch name="_addmore" t:label="addMoreAfterSubmit"/>
15
+ <c:form-switch name="_cloneprev" t:label="clonePrevious"/>
16
16
  </c:grid-col>
17
17
  </c:grid-row>
18
18
  </c:form>
@@ -11,8 +11,8 @@
11
11
  </c:grid-col>
12
12
  <c:grid-col col="6-lg" flex="justify-content:end-lg align-items:center">
13
13
  <c:wdb-btn-delete/>
14
- <c:btn type="reset" color="secondary" t:content="Reset" margin="start-2"/>
15
- <c:btn type="submit" color="primary" t:content="Submit" margin="start-2"/>
14
+ <c:btn type="reset" color="secondary" t:content="reset" margin="start-2"/>
15
+ <c:btn type="submit" color="primary" t:content="submit" margin="start-2"/>
16
16
  </c:grid-col>
17
17
  </c:grid-row>
18
18
  </c:form>
@@ -1,2 +1,2 @@
1
- <c:heading move-to="#fullTitle" type="5" t:content="%s - %s|<%= page.modelTitle %>|<%= _t('Add') %>" />
1
+ <c:heading move-to="#fullTitle" type="5" t:content="%s - %s|<%= page.modelTitle %>|<%= _t('add') %>" />
2
2
  <!-- include waibuDb.partial:/crud/add-handler.html -->
@@ -1,2 +1,2 @@
1
- <c:heading move-to="#fullTitle" type="5" t:content="%s - %s|<%= page.modelTitle %>|<%= _t('Details') %>" />
1
+ <c:heading move-to="#fullTitle" type="5" t:content="%s - %s|<%= page.modelTitle %>|<%= _t('details') %>" />
2
2
  <!-- include waibuDb.partial:/crud/details-handler.html -->
@@ -1,2 +1,2 @@
1
- <c:heading move-to="#fullTitle" type="5" t:content="%s - %s|<%= page.modelTitle %>|<%= _t('Edit') %>" />
1
+ <c:heading move-to="#fullTitle" type="5" t:content="%s - %s|<%= page.modelTitle %>|<%= _t('edit') %>" />
2
2
  <!-- include waibuDb.partial:/crud/edit-handler.html -->
@@ -1,2 +1,2 @@
1
- <c:heading move-to="#fullTitle" type="5" t:content="%s - %s|<%= page.modelTitle %>|<%= _t('List') %>" />
1
+ <c:heading move-to="#fullTitle" type="5" t:content="%s - %s|<%= page.modelTitle %>|<%= _t('list') %>" />
2
2
  <!-- include waibuDb.partial:/crud/list-handler.html -->
@@ -1,8 +1,7 @@
1
1
  <c:div margin="top-5">
2
2
  <c:heading type="3-display" t:content="Action Disabled" margin="bottom-5"/>
3
3
  <c:div>
4
- <c:t value="<%= action %>">Sorry, action '%s' is permanently disabled at database level. For more information,
5
- please contact your Admin immediately, thank you!</c:t>
4
+ <c:t value="<%= action %>">actionPermanentlyDisabled%s</c:t>
6
5
  </c:t>
7
6
  </c:div>
8
7
  </c:div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-db",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "DB Helper",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -26,10 +26,10 @@ function modelsMenu (locals, req) {
26
26
  const items = omenu[k]
27
27
  const plugin = this.app[items[0].ns]
28
28
  menu.push({
29
- name: k,
29
+ title: k,
30
30
  children: map(items, item => {
31
31
  return {
32
- name: req.t(camelCase(item.name.slice(plugin.alias.length))),
32
+ title: camelCase(item.name.slice(plugin.alias.length)),
33
33
  href: `waibuAdmin:/${prefix}/${kebabCase(item.name)}/list`
34
34
  }
35
35
  })
@@ -1,12 +1,12 @@
1
1
  import prepCrud from '../../../lib/prep-crud.js'
2
2
 
3
3
  async function get ({ model, req, reply, id, options = {} }) {
4
- const { recordGet, attachmentFind } = this.app.dobo
5
- const { parseFilter } = this.app.waibu
6
- const { name, recId, opts, attachment, stats, mimeType } = prepCrud.call(this, { model, req, reply, id, options, args: ['model', 'id'] })
7
- opts.filter = parseFilter(req)
8
- const ret = await recordGet(name, recId, opts)
9
- if (attachment) ret.data._attachment = await attachmentFind(name, id, { stats, mimeType })
4
+ const { recordFindOne, attachmentFind } = this.app.dobo
5
+ const { name, filter, opts, attachment, stats, mimeType } = prepCrud.call(this, { model, req, reply, id, options, args: ['model', 'id'] })
6
+ filter.query = { $and: [filter.query ?? {}, { id: id ?? req.params.id }] }
7
+ opts.dataOnly = true
8
+ const ret = await recordFindOne(name, filter, opts)
9
+ if (attachment) ret.data._attachment = await attachmentFind(name, ret.id, { stats, mimeType })
10
10
  return ret
11
11
  }
12
12
 
@@ -13,7 +13,7 @@ async function btnAdd () {
13
13
  this.params.html = ''
14
14
  return
15
15
  }
16
- if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('Add')
16
+ if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('add')
17
17
  this.params.attr.color = this.params.attr.color ?? 'secondary-outline'
18
18
  if (!this.params.attr.href) this.params.attr.href = this.component.buildUrl({ base: 'add' })
19
19
  this.params.html = await this.component.buildTag({ tag: 'btn', attr: this.params.attr, html: this.params.html })
@@ -9,7 +9,7 @@ async function btnBack () {
9
9
  const { attrToArray } = this.plugin.app.waibuMpa
10
10
  const { req } = this.component
11
11
  this.params.noTag = true
12
- if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('Back')
12
+ if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('back')
13
13
  if (isEmpty(this.params.attr.icon)) this.params.attr.icon = 'arrowStart'
14
14
  this.params.attr.color = this.params.attr.color ?? 'secondary-outline'
15
15
  this.params.attr.excludeQs = ['mode', 'id', ...attrToArray(this.params.attr.excludeQs ?? '')]
@@ -13,7 +13,7 @@ async function btnClone () {
13
13
  this.params.html = ''
14
14
  return
15
15
  }
16
- if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('Clone')
16
+ if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('clone')
17
17
  this.params.attr.color = this.params.attr.color ?? 'secondary-outline'
18
18
  if (!this.params.attr.href) this.params.attr.href = this.component.buildUrl({ base: 'add', exclude: ['id'] }) + '&mode=clone'
19
19
  if (this.params.attr.onList) {
@@ -20,7 +20,7 @@ async function btnColumns () {
20
20
  if (isEmpty(fields)) fields = schema.view.fields
21
21
  const items = []
22
22
  this.params.attr.color = this.params.attr.color ?? 'secondary-outline'
23
- if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('Columns')
23
+ if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('columns')
24
24
  for (const f of schema.view.fields) {
25
25
  if (f === 'id') {
26
26
  items.push(await this.component.buildTag({ tag: 'formCheck', attr: { checked: true, label: req.t('ID'), value: f, disabled: true } }))
@@ -44,7 +44,7 @@ async function btnColumns () {
44
44
  ">`)
45
45
  html.push(...items)
46
46
  const attr = { size: 'sm', 'x-ref': 'apply', margin: 'top-2', color: this.params.attr.applyColor ?? 'primary', icon: this.params.attr.applyIcon ?? 'arrowsStartEnd', href }
47
- html.push(await this.component.buildTag({ tag: 'btn', attr, html: req.t('Apply') }))
47
+ html.push(await this.component.buildTag({ tag: 'btn', attr, html: req.t('apply') }))
48
48
  html.push('</form>')
49
49
  this.params.attr.autoClose = 'outside'
50
50
  this.params.attr.triggerColor = this.params.attr.color
@@ -14,7 +14,7 @@ async function btnDelete () {
14
14
  this.params.html = ''
15
15
  return
16
16
  }
17
- if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('Delete')
17
+ if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('delete')
18
18
  this.params.attr.color = this.params.attr.color ?? 'danger-outline'
19
19
  this.params.attr.id = generateId('alpha')
20
20
  if (this.params.attr.onList) {
@@ -41,7 +41,7 @@ async function btnDelete () {
41
41
  }
42
42
  }`
43
43
  }
44
- const msg = 'You\'re about to remove one or more records. Are you really sure to do this?'
44
+ const msg = 'aboutToRemoveRecord'
45
45
  this.params.attr['@click'] = `
46
46
  await wbs.confirmation(\`${req.t(msg)}\`, { ok: '${this.params.attr.id}:remove', close: 'y', opts: selected })
47
47
  `
@@ -14,7 +14,7 @@ async function btnDetails () {
14
14
  this.params.html = ''
15
15
  return
16
16
  }
17
- if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('Details')
17
+ if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('details')
18
18
  this.params.attr.color = this.params.attr.color ?? 'secondary-outline'
19
19
  this.params.attr.id = generateId('alpha')
20
20
  if (!this.params.attr.href) this.params.attr.href = this.component.buildUrl({ base: 'details', exclude: ['id'] })
@@ -14,7 +14,7 @@ async function btnEdit () {
14
14
  this.params.html = ''
15
15
  return
16
16
  }
17
- if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('Edit')
17
+ if (isEmpty(this.params.attr.content)) this.params.attr.content = req.t('edit')
18
18
  this.params.attr.color = this.params.attr.color ?? 'secondary-outline'
19
19
  this.params.attr.id = generateId('alpha')
20
20
  if (!this.params.attr.href) this.params.attr.href = this.component.buildUrl({ base: 'edit', exclude: ['id'] })
@@ -52,7 +52,7 @@ async function btnEdit () {
52
52
  }
53
53
  `
54
54
  const html = [
55
- await this.component.buildTag({ tag: 'dropdownItem', attr: { content: req.t('Add as New Clone') } })
55
+ await this.component.buildTag({ tag: 'dropdownItem', attr: { content: req.t('addAsNewClone') } })
56
56
  ]
57
57
  this.params.attr.triggerColor = this.params.attr.color
58
58
  this.params.html = await this.component.buildTag({ tag: 'dropdown', attr: this.params.attr, html: html.join('\n') })
@@ -4,6 +4,10 @@ async function btnExport () {
4
4
  const WdbBase = await wdbBase.call(this)
5
5
 
6
6
  return class WdbBtnExport extends WdbBase {
7
+ static scripts = [...super.scripts,
8
+ 'waibuMpa.virtual:/json2csv/json2csv.js'
9
+ ]
10
+
7
11
  build = async () => {
8
12
  const { isEmpty, get } = this.plugin.app.bajo.lib._
9
13
  const { req } = this.component
@@ -15,9 +19,9 @@ async function btnExport () {
15
19
  this.params.html = ''
16
20
  return
17
21
  }
18
- if (isEmpty(this.params.attr.trigger)) this.params.attr.trigger = req.t('Export')
22
+ if (isEmpty(this.params.attr.trigger)) this.params.attr.trigger = req.t('export')
19
23
  this.params.attr.triggerColor = this.params.attr.triggerColor ?? 'secondary-outline'
20
- this.params.attr.title = req.t('Data Export')
24
+ this.params.attr.title = req.t('dataExport')
21
25
  const html = await this.component.buildSentence(`
22
26
  <c:div x-data="{
23
27
  delivery: 'clipboard',
@@ -71,8 +75,15 @@ async function btnExport () {
71
75
  for (const el of els) {
72
76
  let data = []
73
77
  _.each(el.children, (v, i) => {
74
- if ((i + '') === '0' && checker) return undefined
75
- data.push(this.options.includes('fvalue') ? v.innerText : wmpa.parseValue(v.dataset.value, types[parseInt(i - 1)]))
78
+ i = i + ''
79
+ if (i === '0' && checker) return undefined
80
+ if (this.options.includes('fvalue')) data.push(v.innerText)
81
+ else {
82
+ const type = types[parseInt(i)]
83
+ let val = wmpa.parseValue(v.dataset.value, type)
84
+ if (['datetime', 'date', 'time'].includes(type)) val = val.toISOString()
85
+ data.push(val)
86
+ }
76
87
  })
77
88
  const item = {}
78
89
  for (const i in keys) {
@@ -105,28 +116,28 @@ async function btnExport () {
105
116
  ">
106
117
  <c:grid-row gutter="2">
107
118
  <c:grid-col col="6-md">
108
- <c:fieldset t:legend="Delivery" legend-type="6">
109
- <c:form-radio x-model="delivery" value="file" t:label="Save as File" />
110
- <c:form-radio x-model="delivery" value="clipboard" t:label="Copy to Clipboard" />
119
+ <c:fieldset t:legend="delivery" legend-type="6">
120
+ <c:form-radio x-model="delivery" value="file" t:label="saveAsFile" />
121
+ <c:form-radio x-model="delivery" value="clipboard" t:label="copyClipboard" />
111
122
  </c:fieldset>
112
- <c:fieldset t:legend="Options" legend-type="6" margin="top-2">
113
- <c:form-check x-ref="fkey" x-model="options" value="fkey" t:label="Formatted Field" />
114
- <c:form-check x-ref="fvalue" x-model="options" value="fvalue" t:label="Formatted Value" />
115
- <c:form-check x-ref="zip" x-model="options" value="zip" t:label="Zipped" />
123
+ <c:fieldset t:legend="options" legend-type="6" margin="top-2">
124
+ <c:form-check x-ref="fkey" x-model="options" value="fkey" t:label="formattedField" />
125
+ <c:form-check x-ref="fvalue" x-model="options" value="fvalue" t:label="formattedValue" />
126
+ <c:form-check x-ref="zip" x-model="options" value="zip" t:label="zipped" />
116
127
  </c:fieldset>
117
128
  </c:grid-col>
118
129
  <c:grid-col col="6-md">
119
- <c:fieldset t:legend="File Type" legend-type="6">
120
- <c:form-radio x-ref="xlsx" x-model="ftype" value="xlsx" t:label="Excel XLSX" />
121
- <c:form-radio x-ref="csv" x-model="ftype" value="csv" t:label="CSV" />
122
- <c:form-radio x-ref="xml" x-model="ftype" value="xml" t:label="XML" />
123
- <c:form-radio x-ref="json" x-model="ftype" value="json" t:label="JSON" />
130
+ <c:fieldset t:legend="fileType" legend-type="6">
131
+ <c:form-radio x-ref="xlsx" x-model="ftype" value="xlsx" t:label="excelXlsx" />
132
+ <c:form-radio x-ref="csv" x-model="ftype" value="csv" t:label="csv" />
133
+ <c:form-radio x-ref="xml" x-model="ftype" value="xml" t:label="xml" />
134
+ <c:form-radio x-ref="json" x-model="ftype" value="json" t:label="json" />
124
135
  </c:fieldset />
125
136
  </c:grid-col>
126
137
  </c:grid-row>
127
138
  <c:div flex="justify-content:end" margin="top-3">
128
- <c:btn color="secondary" t:content="Close" dismiss />
129
- <c:btn color="primary" t:content="Submit" margin="start-2" @click="await submit()" />
139
+ <c:btn color="secondary" t:content="close" dismiss />
140
+ <c:btn color="primary" t:content="submit" margin="start-2" @click="await submit()" />
130
141
  </c:div>
131
142
  </c:div>
132
143
  `)
@@ -51,12 +51,12 @@ async function query () {
51
51
  this.params.noTag = true
52
52
  const container = this.params.attr.modal ? 'modal' : 'drawer'
53
53
  this.params.html = await this.component.buildSentence(`
54
- <c:form-input ${count === 0 ? 'disabled' : ''} type="search" t:placeholder="Query" id="${id}" x-data="{ query: '' }" x-init="
54
+ <c:form-input ${count === 0 ? 'disabled' : ''} type="search" t:placeholder="query" id="${id}" x-data="{ query: '' }" x-init="
55
55
  const url = new URL(window.location.href)
56
56
  query = url.searchParams.get('${qsKey.query}') ?? ''
57
57
  " x-model="query" @on-query.window="query = $event.detail ?? ''" @keyup.enter="$dispatch('on-submit')">
58
58
  <c:form-input-addon>
59
- <c:${container} ${count === 0 ? 'trigger-disabled' : ''} trigger-icon="${this.params.attr.icon ?? 'dotsThree'}" trigger-on-end t:title="Query Builder" x-ref="query" x-data="{
59
+ <c:${container} ${count === 0 ? 'trigger-disabled' : ''} trigger-icon="${this.params.attr.icon ?? 'dotsThree'}" trigger-on-end t:title="queryBuilder" x-ref="query" x-data="{
60
60
  fields: ${jsonStringify(fields, true)},
61
61
  builder: '',
62
62
  selected: [],
@@ -151,14 +151,14 @@ async function query () {
151
151
  ${columns.join('\n')}
152
152
  </c:grid-row>
153
153
  <c:div flex="justify-content:end" margin="top-3">
154
- <c:btn color="secondary" t:content="Close" dismiss="${container}" />
155
- <c:btn color="primary" t:content="Apply" margin="start-2" @click="submit()" />
156
- <c:btn color="primary" t:content="Submit Query" margin="start-2" @click="submit(true)" />
154
+ <c:btn color="secondary" t:content="close" dismiss="${container}" />
155
+ <c:btn color="primary" t:content="apply" margin="start-2" @click="submit()" />
156
+ <c:btn color="primary" t:content="submitQuery" margin="start-2" @click="submit(true)" />
157
157
  </c:div>
158
158
  </c:${container}>
159
159
  </c:form-input-addon>
160
160
  <c:form-input-addon>
161
- <c:btn ${count === 0 ? 'disabled' : ''} t:content="Submit" x-data="{
161
+ <c:btn ${count === 0 ? 'disabled' : ''} t:content="submit" x-data="{
162
162
  submit () {
163
163
  const val = document.getElementById('${id}').value ?? ''
164
164
  const url = new URL(window.location.href)
@@ -30,10 +30,10 @@ async function recsInfo () {
30
30
  if (!this.params.attr.dropdown) this.params.attr.dropdown = true
31
31
  const group = groupAttrs(this.params.attr, ['dropdown'])
32
32
  const html = []
33
- if (this.params.attr.count) html.push(req.t('%s record(s) found', req.format(count, 'integer')))
33
+ if (this.params.attr.count) html.push(req.t('recsFound%s', req.format(count, 'integer')))
34
34
  if (this.params.attr.pages) {
35
35
  if (!isEmpty(html)) html[html.length - 1] += '.'
36
- html.push(req.t('Page %s of %s pages', req.format(page, 'integer'), req.format(pages, 'integer')))
36
+ html.push(req.t('pageOfPages%s%s', req.format(page, 'integer'), req.format(pages, 'integer')))
37
37
  }
38
38
  if (this.params.attr.recsPerPage) {
39
39
  this.params.attr.recsPerPageValues = this.params.attr.recsPerPageValues ?? '10 25 50'
@@ -48,7 +48,7 @@ async function recsInfo () {
48
48
  attr.content = limit + ''
49
49
  attr.color = attr.color ?? 'secondary-outline'
50
50
  html.push(await this.component.buildTag({ tag: 'dropdown', attr, html: items.join('\n') }))
51
- html.push(' ', req.t('recs per page'))
51
+ html.push(' ', req.t('recsPerPage'))
52
52
  }
53
53
  this.params.attr = omit(this.params.attr, ['count', 'pages', 'recsPerPage', 'dropdown', 'recsPerPageValues'])
54
54
  this.params.html = html.map(h => `<div class="me-1">${h}</div>`).join('\n')
@@ -29,9 +29,10 @@ async function table () {
29
29
  const prettyUrl = this.params.attr.prettyUrl
30
30
 
31
31
  const data = get(this, 'component.locals.list.data', [])
32
+ const filter = get(this, 'component.locals.list.filter', {})
32
33
  const count = get(this, 'component.locals.list.count', 0)
33
34
  if (count === 0) {
34
- const alert = '<c:alert color="warning" t:content="No record found" margin="top-4"/>'
35
+ const alert = '<c:alert color="warning" t:content="noRecordFound" margin="top-4"/>'
35
36
  this.params.noTag = true
36
37
  this.params.html = await this.component.buildSentence(alert)
37
38
  return
@@ -45,7 +46,11 @@ async function table () {
45
46
  const qsKey = this.plugin.app.waibu.config.qsKey
46
47
  let fields = without(get(this, `component.locals._meta.query.${qsKey.fields}`, '').split(','), '')
47
48
  if (isEmpty(fields)) fields = schema.view.fields
48
- const sort = this.params.attr.sort ? attrToArray(this.params.attr.sort) : get(this, `component.locals._meta.query.${qsKey.sort}`, '')
49
+ let sort = this.params.attr.sort ? attrToArray(this.params.attr.sort) : get(this, `component.locals._meta.query.${qsKey.sort}`, '')
50
+ if (isEmpty(sort)) {
51
+ const keys = Object.keys(filter.sort)
52
+ if (keys.length > 0) sort = `${keys[0]}:${filter.sort[keys[0]]}`
53
+ }
49
54
 
50
55
  let [sortCol, sortDir] = sort.split(':')
51
56
  if (!['-1', '1'].includes(sortDir)) sortDir = '1'
@@ -63,7 +68,8 @@ async function table () {
63
68
  // head
64
69
  for (const f of schema.view.fields) {
65
70
  if (!fields.includes(f)) continue
66
- const prop = find(schema.properties, { name: f })
71
+ let prop = find(schema.properties, { name: f })
72
+ if (!prop) prop = find(schema.view.calcFields, { name: f })
67
73
  if (!prop) continue
68
74
  let head = req.t(get(schema, `view.label.${f}`, `field.${f}`))
69
75
  if (!this.params.attr.noSort && (schema.sortables ?? []).includes(f)) {
@@ -113,7 +119,8 @@ async function table () {
113
119
  }
114
120
  for (const f of schema.view.fields) {
115
121
  if (!fields.includes(f)) continue
116
- const prop = find(schema.properties, { name: f })
122
+ let prop = find(schema.properties, { name: f })
123
+ if (!prop) prop = find(schema.view.calcFields, { name: f })
117
124
  if (!prop) continue
118
125
  const opts = {}
119
126
  if (f === 'lng') opts.longitude = true
@@ -124,8 +131,14 @@ async function table () {
124
131
  ' ' + (req.t(d[f] ? 'Yes' : 'No'))
125
132
  } else value = escape(value)
126
133
  let dataValue = d[f] ?? ''
134
+ if (['datetime'].includes(prop.type)) dataValue = escape(dataValue.toISOString())
127
135
  if (['string', 'text'].includes(prop.type)) dataValue = escape(dataValue)
128
136
  if (['array', 'object'].includes(prop.type)) dataValue = escape(JSON.stringify(d[f]))
137
+ const vf = get(schema, `view.valueFormatter.${f}`)
138
+ if (vf) {
139
+ if (isFunction(vf)) dataValue = escape(await vf(d[f], d))
140
+ else dataValue = await callHandler(vf, req, d[f], d)
141
+ }
129
142
  const attr = { dataValue, dataKey: prop.name, dataType: prop.type, style: { cursor: 'pointer' } }
130
143
  const cellFormatter = get(schema, `view.cellFormatter.${f}`)
131
144
  if (cellFormatter) merge(attr, await cellFormatter(dataValue, d))
@@ -158,6 +171,7 @@ async function table () {
158
171
  const goDetails = `
159
172
  goDetails (id) {
160
173
  let url = '${this.params.attr.detailsHref ?? this.component.buildUrl({ base: 'details', prettyUrl })}'
174
+ if (url === '#') return
161
175
  if (url.indexOf('/:id') > -1) url = url.replace('/:id', '/' + id)
162
176
  else url += '&id=' + id
163
177
  window.location.href = url