From 965834103187f690bd2ae9b91ddc732b01494059 Mon Sep 17 00:00:00 2001
From: Flatlogic Bot
Date: Thu, 18 Sep 2025 16:05:59 +0000
Subject: [PATCH] Auto commit: 2025-09-18T16:05:59.956Z
---
app-shell/src/_schema.json | 2 +-
backend/src/db/api/books.js | 258 ++++++++
backend/src/db/api/chapters.js | 315 +++++++++
backend/src/db/api/notes.js | 2 +-
backend/src/db/api/readings.js | 371 ++++++++++-
backend/src/db/api/translations.js | 267 ++++++++
backend/src/db/migrations/1758211105293.js | 54 ++
backend/src/db/migrations/1758211132842.js | 54 ++
backend/src/db/migrations/1758211158025.js | 36 ++
backend/src/db/migrations/1758211187600.js | 54 ++
backend/src/db/migrations/1758211229331.js | 47 ++
backend/src/db/models/books.js | 73 +++
backend/src/db/models/chapters.js | 73 +++
backend/src/db/models/readings.js | 88 +++
backend/src/db/models/translations.js | 65 ++
.../db/seeders/20231127130745-sample-data.js | 600 ++++++++++++++++++
backend/src/routes/readings.js | 4 +
backend/src/services/search.js | 10 +-
.../src/components/Readings/CardReadings.tsx | 107 +++-
.../src/components/Readings/ListReadings.tsx | 81 +++
.../Readings/configureReadingsCols.tsx | 170 +++++
.../components/WebPageComponents/Footer.tsx | 4 +-
.../components/WebPageComponents/Header.tsx | 4 +-
frontend/src/helpers/dataFormatter.js | 84 ++-
frontend/src/pages/books/books-view.tsx | 236 +++++++
frontend/src/pages/chapters/chapters-view.tsx | 207 ++++++
frontend/src/pages/index.tsx | 2 +-
frontend/src/pages/notes/[notesId].tsx | 2 +-
frontend/src/pages/notes/notes-edit.tsx | 2 +-
frontend/src/pages/notes/notes-view.tsx | 2 +-
frontend/src/pages/readings/[readingsId].tsx | 113 ++++
frontend/src/pages/readings/readings-edit.tsx | 113 ++++
frontend/src/pages/readings/readings-list.tsx | 16 +
frontend/src/pages/readings/readings-new.tsx | 105 +++
.../src/pages/readings/readings-table.tsx | 16 +
frontend/src/pages/readings/readings-view.tsx | 111 ++++
.../pages/translations/translations-view.tsx | 205 ++++++
frontend/src/pages/users/users-view.tsx | 4 +
38 files changed, 3937 insertions(+), 20 deletions(-)
create mode 100644 backend/src/db/api/books.js
create mode 100644 backend/src/db/api/chapters.js
create mode 100644 backend/src/db/api/translations.js
create mode 100644 backend/src/db/migrations/1758211105293.js
create mode 100644 backend/src/db/migrations/1758211132842.js
create mode 100644 backend/src/db/migrations/1758211158025.js
create mode 100644 backend/src/db/migrations/1758211187600.js
create mode 100644 backend/src/db/migrations/1758211229331.js
create mode 100644 backend/src/db/models/books.js
create mode 100644 backend/src/db/models/chapters.js
create mode 100644 backend/src/db/models/translations.js
create mode 100644 frontend/src/pages/books/books-view.tsx
create mode 100644 frontend/src/pages/chapters/chapters-view.tsx
create mode 100644 frontend/src/pages/translations/translations-view.tsx
diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json
index 78c2555..0451ae1 100644
--- a/app-shell/src/_schema.json
+++ b/app-shell/src/_schema.json
@@ -1,4 +1,4 @@
{
"Initial version": "{\"iv\":\"4z7s2d9i59CEXx/W\",\"encryptedData\":\"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\"}",
- "": "{\"iv\":\"Hvk33yAs8JkTqeFJ\",\"encryptedData\":\"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\"}"
+ "": "{\"iv\":\"/TUYwyFueIOjbWpo\",\"encryptedData\":\"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\"}"
}
\ No newline at end of file
diff --git a/backend/src/db/api/books.js b/backend/src/db/api/books.js
new file mode 100644
index 0000000..25028a0
--- /dev/null
+++ b/backend/src/db/api/books.js
@@ -0,0 +1,258 @@
+const db = require('../models');
+const FileDBApi = require('./file');
+const crypto = require('crypto');
+const Utils = require('../utils');
+
+const Sequelize = db.Sequelize;
+const Op = Sequelize.Op;
+
+module.exports = class BooksDBApi {
+ static async create(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const books = await db.books.create(
+ {
+ id: data.id || undefined,
+
+ name: data.name || null,
+ importHash: data.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ },
+ { transaction },
+ );
+
+ return books;
+ }
+
+ static async bulkImport(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ // Prepare data - wrapping individual data transformations in a map() method
+ const booksData = data.map((item, index) => ({
+ id: item.id || undefined,
+
+ name: item.name || null,
+ importHash: item.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ createdAt: new Date(Date.now() + index * 1000),
+ }));
+
+ // Bulk create items
+ const books = await db.books.bulkCreate(booksData, { transaction });
+
+ // For each item created, replace relation files
+
+ return books;
+ }
+
+ static async update(id, data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const books = await db.books.findByPk(id, {}, { transaction });
+
+ const updatePayload = {};
+
+ if (data.name !== undefined) updatePayload.name = data.name;
+
+ updatePayload.updatedById = currentUser.id;
+
+ await books.update(updatePayload, { transaction });
+
+ return books;
+ }
+
+ static async deleteByIds(ids, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const books = await db.books.findAll({
+ where: {
+ id: {
+ [Op.in]: ids,
+ },
+ },
+ transaction,
+ });
+
+ await db.sequelize.transaction(async (transaction) => {
+ for (const record of books) {
+ await record.update({ deletedBy: currentUser.id }, { transaction });
+ }
+ for (const record of books) {
+ await record.destroy({ transaction });
+ }
+ });
+
+ return books;
+ }
+
+ static async remove(id, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const books = await db.books.findByPk(id, options);
+
+ await books.update(
+ {
+ deletedBy: currentUser.id,
+ },
+ {
+ transaction,
+ },
+ );
+
+ await books.destroy({
+ transaction,
+ });
+
+ return books;
+ }
+
+ static async findBy(where, options) {
+ const transaction = (options && options.transaction) || undefined;
+
+ const books = await db.books.findOne({ where }, { transaction });
+
+ if (!books) {
+ return books;
+ }
+
+ const output = books.get({ plain: true });
+
+ output.readings_bookref = await books.getReadings_bookref({
+ transaction,
+ });
+
+ output.readings_book_select = await books.getReadings_book_select({
+ transaction,
+ });
+
+ output.chapters_book = await books.getChapters_book({
+ transaction,
+ });
+
+ return output;
+ }
+
+ static async findAll(filter, options) {
+ const limit = filter.limit || 0;
+ let offset = 0;
+ let where = {};
+ const currentPage = +filter.page;
+
+ offset = currentPage * limit;
+
+ const orderBy = null;
+
+ const transaction = (options && options.transaction) || undefined;
+
+ let include = [];
+
+ if (filter) {
+ if (filter.id) {
+ where = {
+ ...where,
+ ['id']: Utils.uuid(filter.id),
+ };
+ }
+
+ if (filter.name) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike('books', 'name', filter.name),
+ };
+ }
+
+ if (filter.active !== undefined) {
+ where = {
+ ...where,
+ active: filter.active === true || filter.active === 'true',
+ };
+ }
+
+ if (filter.createdAtRange) {
+ const [start, end] = filter.createdAtRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+ }
+
+ const queryOptions = {
+ where,
+ include,
+ distinct: true,
+ order:
+ filter.field && filter.sort
+ ? [[filter.field, filter.sort]]
+ : [['createdAt', 'desc']],
+ transaction: options?.transaction,
+ logging: console.log,
+ };
+
+ if (!options?.countOnly) {
+ queryOptions.limit = limit ? Number(limit) : undefined;
+ queryOptions.offset = offset ? Number(offset) : undefined;
+ }
+
+ try {
+ const { rows, count } = await db.books.findAndCountAll(queryOptions);
+
+ return {
+ rows: options?.countOnly ? [] : rows,
+ count: count,
+ };
+ } catch (error) {
+ console.error('Error executing query:', error);
+ throw error;
+ }
+ }
+
+ static async findAllAutocomplete(query, limit, offset) {
+ let where = {};
+
+ if (query) {
+ where = {
+ [Op.or]: [
+ { ['id']: Utils.uuid(query) },
+ Utils.ilike('books', 'name', query),
+ ],
+ };
+ }
+
+ const records = await db.books.findAll({
+ attributes: ['id', 'name'],
+ where,
+ limit: limit ? Number(limit) : undefined,
+ offset: offset ? Number(offset) : undefined,
+ orderBy: [['name', 'ASC']],
+ });
+
+ return records.map((record) => ({
+ id: record.id,
+ label: record.name,
+ }));
+ }
+};
diff --git a/backend/src/db/api/chapters.js b/backend/src/db/api/chapters.js
new file mode 100644
index 0000000..df3976a
--- /dev/null
+++ b/backend/src/db/api/chapters.js
@@ -0,0 +1,315 @@
+const db = require('../models');
+const FileDBApi = require('./file');
+const crypto = require('crypto');
+const Utils = require('../utils');
+
+const Sequelize = db.Sequelize;
+const Op = Sequelize.Op;
+
+module.exports = class ChaptersDBApi {
+ static async create(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const chapters = await db.chapters.create(
+ {
+ id: data.id || undefined,
+
+ number: data.number || null,
+ importHash: data.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ },
+ { transaction },
+ );
+
+ await chapters.setBook(data.book || null, {
+ transaction,
+ });
+
+ return chapters;
+ }
+
+ static async bulkImport(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ // Prepare data - wrapping individual data transformations in a map() method
+ const chaptersData = data.map((item, index) => ({
+ id: item.id || undefined,
+
+ number: item.number || null,
+ importHash: item.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ createdAt: new Date(Date.now() + index * 1000),
+ }));
+
+ // Bulk create items
+ const chapters = await db.chapters.bulkCreate(chaptersData, {
+ transaction,
+ });
+
+ // For each item created, replace relation files
+
+ return chapters;
+ }
+
+ static async update(id, data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const chapters = await db.chapters.findByPk(id, {}, { transaction });
+
+ const updatePayload = {};
+
+ if (data.number !== undefined) updatePayload.number = data.number;
+
+ updatePayload.updatedById = currentUser.id;
+
+ await chapters.update(updatePayload, { transaction });
+
+ if (data.book !== undefined) {
+ await chapters.setBook(
+ data.book,
+
+ { transaction },
+ );
+ }
+
+ return chapters;
+ }
+
+ static async deleteByIds(ids, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const chapters = await db.chapters.findAll({
+ where: {
+ id: {
+ [Op.in]: ids,
+ },
+ },
+ transaction,
+ });
+
+ await db.sequelize.transaction(async (transaction) => {
+ for (const record of chapters) {
+ await record.update({ deletedBy: currentUser.id }, { transaction });
+ }
+ for (const record of chapters) {
+ await record.destroy({ transaction });
+ }
+ });
+
+ return chapters;
+ }
+
+ static async remove(id, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const chapters = await db.chapters.findByPk(id, options);
+
+ await chapters.update(
+ {
+ deletedBy: currentUser.id,
+ },
+ {
+ transaction,
+ },
+ );
+
+ await chapters.destroy({
+ transaction,
+ });
+
+ return chapters;
+ }
+
+ static async findBy(where, options) {
+ const transaction = (options && options.transaction) || undefined;
+
+ const chapters = await db.chapters.findOne({ where }, { transaction });
+
+ if (!chapters) {
+ return chapters;
+ }
+
+ const output = chapters.get({ plain: true });
+
+ output.readings_chapterref = await chapters.getReadings_chapterref({
+ transaction,
+ });
+
+ output.readings_chapter_select = await chapters.getReadings_chapter_select({
+ transaction,
+ });
+
+ output.book = await chapters.getBook({
+ transaction,
+ });
+
+ return output;
+ }
+
+ static async findAll(filter, options) {
+ const limit = filter.limit || 0;
+ let offset = 0;
+ let where = {};
+ const currentPage = +filter.page;
+
+ offset = currentPage * limit;
+
+ const orderBy = null;
+
+ const transaction = (options && options.transaction) || undefined;
+
+ let include = [
+ {
+ model: db.books,
+ as: 'book',
+
+ where: filter.book
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.book
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ name: {
+ [Op.or]: filter.book
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+ ];
+
+ if (filter) {
+ if (filter.id) {
+ where = {
+ ...where,
+ ['id']: Utils.uuid(filter.id),
+ };
+ }
+
+ if (filter.numberRange) {
+ const [start, end] = filter.numberRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ number: {
+ ...where.number,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ number: {
+ ...where.number,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.active !== undefined) {
+ where = {
+ ...where,
+ active: filter.active === true || filter.active === 'true',
+ };
+ }
+
+ if (filter.createdAtRange) {
+ const [start, end] = filter.createdAtRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+ }
+
+ const queryOptions = {
+ where,
+ include,
+ distinct: true,
+ order:
+ filter.field && filter.sort
+ ? [[filter.field, filter.sort]]
+ : [['createdAt', 'desc']],
+ transaction: options?.transaction,
+ logging: console.log,
+ };
+
+ if (!options?.countOnly) {
+ queryOptions.limit = limit ? Number(limit) : undefined;
+ queryOptions.offset = offset ? Number(offset) : undefined;
+ }
+
+ try {
+ const { rows, count } = await db.chapters.findAndCountAll(queryOptions);
+
+ return {
+ rows: options?.countOnly ? [] : rows,
+ count: count,
+ };
+ } catch (error) {
+ console.error('Error executing query:', error);
+ throw error;
+ }
+ }
+
+ static async findAllAutocomplete(query, limit, offset) {
+ let where = {};
+
+ if (query) {
+ where = {
+ [Op.or]: [
+ { ['id']: Utils.uuid(query) },
+ Utils.ilike('chapters', 'number', query),
+ ],
+ };
+ }
+
+ const records = await db.chapters.findAll({
+ attributes: ['id', 'number'],
+ where,
+ limit: limit ? Number(limit) : undefined,
+ offset: offset ? Number(offset) : undefined,
+ orderBy: [['number', 'ASC']],
+ });
+
+ return records.map((record) => ({
+ id: record.id,
+ label: record.number,
+ }));
+ }
+};
diff --git a/backend/src/db/api/notes.js b/backend/src/db/api/notes.js
index 30433f5..c28972d 100644
--- a/backend/src/db/api/notes.js
+++ b/backend/src/db/api/notes.js
@@ -191,7 +191,7 @@ module.exports = class NotesDBApi {
},
},
{
- book: {
+ tags: {
[Op.or]: filter.reading
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
diff --git a/backend/src/db/api/readings.js b/backend/src/db/api/readings.js
index f7ad6aa..4eb87f0 100644
--- a/backend/src/db/api/readings.js
+++ b/backend/src/db/api/readings.js
@@ -21,6 +21,7 @@ module.exports = class ReadingsDBApi {
verses: data.verses || null,
translation: data.translation || null,
notes: data.notes || null,
+ tags: data.tags || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@@ -32,6 +33,38 @@ module.exports = class ReadingsDBApi {
transaction,
});
+ await readings.setBookref(data.bookref || null, {
+ transaction,
+ });
+
+ await readings.setChapterref(data.chapterref || null, {
+ transaction,
+ });
+
+ await readings.setTranslationref(data.translationref || null, {
+ transaction,
+ });
+
+ await readings.setBook_select(data.book_select || null, {
+ transaction,
+ });
+
+ await readings.setChapter_select(data.chapter_select || null, {
+ transaction,
+ });
+
+ await readings.setTranslation_select(data.translation_select || null, {
+ transaction,
+ });
+
+ await readings.setVersesref(data.versesref || [], {
+ transaction,
+ });
+
+ await readings.setVerses_select(data.verses_select || [], {
+ transaction,
+ });
+
return readings;
}
@@ -49,6 +82,7 @@ module.exports = class ReadingsDBApi {
verses: item.verses || null,
translation: item.translation || null,
notes: item.notes || null,
+ tags: item.tags || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@@ -86,6 +120,8 @@ module.exports = class ReadingsDBApi {
if (data.notes !== undefined) updatePayload.notes = data.notes;
+ if (data.tags !== undefined) updatePayload.tags = data.tags;
+
updatePayload.updatedById = currentUser.id;
await readings.update(updatePayload, { transaction });
@@ -98,6 +134,62 @@ module.exports = class ReadingsDBApi {
);
}
+ if (data.bookref !== undefined) {
+ await readings.setBookref(
+ data.bookref,
+
+ { transaction },
+ );
+ }
+
+ if (data.chapterref !== undefined) {
+ await readings.setChapterref(
+ data.chapterref,
+
+ { transaction },
+ );
+ }
+
+ if (data.translationref !== undefined) {
+ await readings.setTranslationref(
+ data.translationref,
+
+ { transaction },
+ );
+ }
+
+ if (data.book_select !== undefined) {
+ await readings.setBook_select(
+ data.book_select,
+
+ { transaction },
+ );
+ }
+
+ if (data.chapter_select !== undefined) {
+ await readings.setChapter_select(
+ data.chapter_select,
+
+ { transaction },
+ );
+ }
+
+ if (data.translation_select !== undefined) {
+ await readings.setTranslation_select(
+ data.translation_select,
+
+ { transaction },
+ );
+ }
+
+ if (data.versesref !== undefined) {
+ await readings.setVersesref(data.versesref, { transaction });
+ }
+
+ if (data.verses_select !== undefined) {
+ await readings.setVerses_select(data.verses_select, { transaction });
+ }
+
return readings;
}
@@ -167,6 +259,38 @@ module.exports = class ReadingsDBApi {
transaction,
});
+ output.bookref = await readings.getBookref({
+ transaction,
+ });
+
+ output.chapterref = await readings.getChapterref({
+ transaction,
+ });
+
+ output.versesref = await readings.getVersesref({
+ transaction,
+ });
+
+ output.translationref = await readings.getTranslationref({
+ transaction,
+ });
+
+ output.book_select = await readings.getBook_select({
+ transaction,
+ });
+
+ output.chapter_select = await readings.getChapter_select({
+ transaction,
+ });
+
+ output.verses_select = await readings.getVerses_select({
+ transaction,
+ });
+
+ output.translation_select = await readings.getTranslation_select({
+ transaction,
+ });
+
return output;
}
@@ -208,6 +332,174 @@ module.exports = class ReadingsDBApi {
}
: {},
},
+
+ {
+ model: db.books,
+ as: 'bookref',
+
+ where: filter.bookref
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.bookref
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ name: {
+ [Op.or]: filter.bookref
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+
+ {
+ model: db.chapters,
+ as: 'chapterref',
+
+ where: filter.chapterref
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.chapterref
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ number: {
+ [Op.or]: filter.chapterref
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+
+ {
+ model: db.translations,
+ as: 'translationref',
+
+ where: filter.translationref
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.translationref
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ name: {
+ [Op.or]: filter.translationref
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+
+ {
+ model: db.books,
+ as: 'book_select',
+
+ where: filter.book_select
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.book_select
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ name: {
+ [Op.or]: filter.book_select
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+
+ {
+ model: db.chapters,
+ as: 'chapter_select',
+
+ where: filter.chapter_select
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.chapter_select
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ number: {
+ [Op.or]: filter.chapter_select
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+
+ {
+ model: db.translations,
+ as: 'translation_select',
+
+ where: filter.translation_select
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.translation_select
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ name: {
+ [Op.or]: filter.translation_select
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+
+ {
+ model: db.verses,
+ as: 'versesref',
+ required: false,
+ },
+
+ {
+ model: db.verses,
+ as: 'verses_select',
+ required: false,
+ },
];
if (filter) {
@@ -246,6 +538,13 @@ module.exports = class ReadingsDBApi {
};
}
+ if (filter.tags) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike('readings', 'tags', filter.tags),
+ };
+ }
+
if (filter.calendarStart && filter.calendarEnd) {
where = {
...where,
@@ -319,6 +618,70 @@ module.exports = class ReadingsDBApi {
};
}
+ if (filter.versesref) {
+ const searchTerms = filter.versesref.split('|');
+
+ include = [
+ {
+ model: db.verses,
+ as: 'versesref_filter',
+ required: searchTerms.length > 0,
+ where:
+ searchTerms.length > 0
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: searchTerms.map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ number: {
+ [Op.or]: searchTerms.map((term) => ({
+ [Op.iLike]: `%${term}%`,
+ })),
+ },
+ },
+ ],
+ }
+ : undefined,
+ },
+ ...include,
+ ];
+ }
+
+ if (filter.verses_select) {
+ const searchTerms = filter.verses_select.split('|');
+
+ include = [
+ {
+ model: db.verses,
+ as: 'verses_select_filter',
+ required: searchTerms.length > 0,
+ where:
+ searchTerms.length > 0
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: searchTerms.map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ number: {
+ [Op.or]: searchTerms.map((term) => ({
+ [Op.iLike]: `%${term}%`,
+ })),
+ },
+ },
+ ],
+ }
+ : undefined,
+ },
+ ...include,
+ ];
+ }
+
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
@@ -381,22 +744,22 @@ module.exports = class ReadingsDBApi {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
- Utils.ilike('readings', 'book', query),
+ Utils.ilike('readings', 'tags', query),
],
};
}
const records = await db.readings.findAll({
- attributes: ['id', 'book'],
+ attributes: ['id', 'tags'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
- orderBy: [['book', 'ASC']],
+ orderBy: [['tags', 'ASC']],
});
return records.map((record) => ({
id: record.id,
- label: record.book,
+ label: record.tags,
}));
}
};
diff --git a/backend/src/db/api/translations.js b/backend/src/db/api/translations.js
new file mode 100644
index 0000000..60aed39
--- /dev/null
+++ b/backend/src/db/api/translations.js
@@ -0,0 +1,267 @@
+const db = require('../models');
+const FileDBApi = require('./file');
+const crypto = require('crypto');
+const Utils = require('../utils');
+
+const Sequelize = db.Sequelize;
+const Op = Sequelize.Op;
+
+module.exports = class TranslationsDBApi {
+ static async create(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const translations = await db.translations.create(
+ {
+ id: data.id || undefined,
+
+ name: data.name || null,
+ importHash: data.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ },
+ { transaction },
+ );
+
+ return translations;
+ }
+
+ static async bulkImport(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ // Prepare data - wrapping individual data transformations in a map() method
+ const translationsData = data.map((item, index) => ({
+ id: item.id || undefined,
+
+ name: item.name || null,
+ importHash: item.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ createdAt: new Date(Date.now() + index * 1000),
+ }));
+
+ // Bulk create items
+ const translations = await db.translations.bulkCreate(translationsData, {
+ transaction,
+ });
+
+ // For each item created, replace relation files
+
+ return translations;
+ }
+
+ static async update(id, data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const translations = await db.translations.findByPk(
+ id,
+ {},
+ { transaction },
+ );
+
+ const updatePayload = {};
+
+ if (data.name !== undefined) updatePayload.name = data.name;
+
+ updatePayload.updatedById = currentUser.id;
+
+ await translations.update(updatePayload, { transaction });
+
+ return translations;
+ }
+
+ static async deleteByIds(ids, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const translations = await db.translations.findAll({
+ where: {
+ id: {
+ [Op.in]: ids,
+ },
+ },
+ transaction,
+ });
+
+ await db.sequelize.transaction(async (transaction) => {
+ for (const record of translations) {
+ await record.update({ deletedBy: currentUser.id }, { transaction });
+ }
+ for (const record of translations) {
+ await record.destroy({ transaction });
+ }
+ });
+
+ return translations;
+ }
+
+ static async remove(id, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const translations = await db.translations.findByPk(id, options);
+
+ await translations.update(
+ {
+ deletedBy: currentUser.id,
+ },
+ {
+ transaction,
+ },
+ );
+
+ await translations.destroy({
+ transaction,
+ });
+
+ return translations;
+ }
+
+ static async findBy(where, options) {
+ const transaction = (options && options.transaction) || undefined;
+
+ const translations = await db.translations.findOne(
+ { where },
+ { transaction },
+ );
+
+ if (!translations) {
+ return translations;
+ }
+
+ const output = translations.get({ plain: true });
+
+ output.readings_translationref =
+ await translations.getReadings_translationref({
+ transaction,
+ });
+
+ output.readings_translation_select =
+ await translations.getReadings_translation_select({
+ transaction,
+ });
+
+ return output;
+ }
+
+ static async findAll(filter, options) {
+ const limit = filter.limit || 0;
+ let offset = 0;
+ let where = {};
+ const currentPage = +filter.page;
+
+ offset = currentPage * limit;
+
+ const orderBy = null;
+
+ const transaction = (options && options.transaction) || undefined;
+
+ let include = [];
+
+ if (filter) {
+ if (filter.id) {
+ where = {
+ ...where,
+ ['id']: Utils.uuid(filter.id),
+ };
+ }
+
+ if (filter.name) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike('translations', 'name', filter.name),
+ };
+ }
+
+ if (filter.active !== undefined) {
+ where = {
+ ...where,
+ active: filter.active === true || filter.active === 'true',
+ };
+ }
+
+ if (filter.createdAtRange) {
+ const [start, end] = filter.createdAtRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+ }
+
+ const queryOptions = {
+ where,
+ include,
+ distinct: true,
+ order:
+ filter.field && filter.sort
+ ? [[filter.field, filter.sort]]
+ : [['createdAt', 'desc']],
+ transaction: options?.transaction,
+ logging: console.log,
+ };
+
+ if (!options?.countOnly) {
+ queryOptions.limit = limit ? Number(limit) : undefined;
+ queryOptions.offset = offset ? Number(offset) : undefined;
+ }
+
+ try {
+ const { rows, count } = await db.translations.findAndCountAll(
+ queryOptions,
+ );
+
+ return {
+ rows: options?.countOnly ? [] : rows,
+ count: count,
+ };
+ } catch (error) {
+ console.error('Error executing query:', error);
+ throw error;
+ }
+ }
+
+ static async findAllAutocomplete(query, limit, offset) {
+ let where = {};
+
+ if (query) {
+ where = {
+ [Op.or]: [
+ { ['id']: Utils.uuid(query) },
+ Utils.ilike('translations', 'name', query),
+ ],
+ };
+ }
+
+ const records = await db.translations.findAll({
+ attributes: ['id', 'name'],
+ where,
+ limit: limit ? Number(limit) : undefined,
+ offset: offset ? Number(offset) : undefined,
+ orderBy: [['name', 'ASC']],
+ });
+
+ return records.map((record) => ({
+ id: record.id,
+ label: record.name,
+ }));
+ }
+};
diff --git a/backend/src/db/migrations/1758211105293.js b/backend/src/db/migrations/1758211105293.js
new file mode 100644
index 0000000..947e8da
--- /dev/null
+++ b/backend/src/db/migrations/1758211105293.js
@@ -0,0 +1,54 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'readings',
+ 'book_selectId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'books',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('readings', 'book_selectId', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1758211132842.js b/backend/src/db/migrations/1758211132842.js
new file mode 100644
index 0000000..a4bf7e9
--- /dev/null
+++ b/backend/src/db/migrations/1758211132842.js
@@ -0,0 +1,54 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'readings',
+ 'chapter_selectId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'chapters',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('readings', 'chapter_selectId', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1758211158025.js b/backend/src/db/migrations/1758211158025.js
new file mode 100644
index 0000000..e6bfba3
--- /dev/null
+++ b/backend/src/db/migrations/1758211158025.js
@@ -0,0 +1,36 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1758211187600.js b/backend/src/db/migrations/1758211187600.js
new file mode 100644
index 0000000..caa7df7
--- /dev/null
+++ b/backend/src/db/migrations/1758211187600.js
@@ -0,0 +1,54 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'readings',
+ 'translation_selectId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'translations',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('readings', 'translation_selectId', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1758211229331.js b/backend/src/db/migrations/1758211229331.js
new file mode 100644
index 0000000..e442ccc
--- /dev/null
+++ b/backend/src/db/migrations/1758211229331.js
@@ -0,0 +1,47 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'readings',
+ 'tags',
+ {
+ type: Sequelize.DataTypes.TEXT,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('readings', 'tags', { transaction });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/models/books.js b/backend/src/db/models/books.js
new file mode 100644
index 0000000..eefda63
--- /dev/null
+++ b/backend/src/db/models/books.js
@@ -0,0 +1,73 @@
+const config = require('../../config');
+const providers = config.providers;
+const crypto = require('crypto');
+const bcrypt = require('bcrypt');
+const moment = require('moment');
+
+module.exports = function (sequelize, DataTypes) {
+ const books = sequelize.define(
+ 'books',
+ {
+ id: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+
+ name: {
+ type: DataTypes.TEXT,
+ },
+
+ importHash: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ {
+ timestamps: true,
+ paranoid: true,
+ freezeTableName: true,
+ },
+ );
+
+ books.associate = (db) => {
+ /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
+
+ db.books.hasMany(db.readings, {
+ as: 'readings_bookref',
+ foreignKey: {
+ name: 'bookrefId',
+ },
+ constraints: false,
+ });
+
+ db.books.hasMany(db.readings, {
+ as: 'readings_book_select',
+ foreignKey: {
+ name: 'book_selectId',
+ },
+ constraints: false,
+ });
+
+ db.books.hasMany(db.chapters, {
+ as: 'chapters_book',
+ foreignKey: {
+ name: 'bookId',
+ },
+ constraints: false,
+ });
+
+ //end loop
+
+ db.books.belongsTo(db.users, {
+ as: 'createdBy',
+ });
+
+ db.books.belongsTo(db.users, {
+ as: 'updatedBy',
+ });
+ };
+
+ return books;
+};
diff --git a/backend/src/db/models/chapters.js b/backend/src/db/models/chapters.js
new file mode 100644
index 0000000..5cb86b2
--- /dev/null
+++ b/backend/src/db/models/chapters.js
@@ -0,0 +1,73 @@
+const config = require('../../config');
+const providers = config.providers;
+const crypto = require('crypto');
+const bcrypt = require('bcrypt');
+const moment = require('moment');
+
+module.exports = function (sequelize, DataTypes) {
+ const chapters = sequelize.define(
+ 'chapters',
+ {
+ id: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+
+ number: {
+ type: DataTypes.INTEGER,
+ },
+
+ importHash: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ {
+ timestamps: true,
+ paranoid: true,
+ freezeTableName: true,
+ },
+ );
+
+ chapters.associate = (db) => {
+ /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
+
+ db.chapters.hasMany(db.readings, {
+ as: 'readings_chapterref',
+ foreignKey: {
+ name: 'chapterrefId',
+ },
+ constraints: false,
+ });
+
+ db.chapters.hasMany(db.readings, {
+ as: 'readings_chapter_select',
+ foreignKey: {
+ name: 'chapter_selectId',
+ },
+ constraints: false,
+ });
+
+ //end loop
+
+ db.chapters.belongsTo(db.books, {
+ as: 'book',
+ foreignKey: {
+ name: 'bookId',
+ },
+ constraints: false,
+ });
+
+ db.chapters.belongsTo(db.users, {
+ as: 'createdBy',
+ });
+
+ db.chapters.belongsTo(db.users, {
+ as: 'updatedBy',
+ });
+ };
+
+ return chapters;
+};
diff --git a/backend/src/db/models/readings.js b/backend/src/db/models/readings.js
index 5147926..52342ee 100644
--- a/backend/src/db/models/readings.js
+++ b/backend/src/db/models/readings.js
@@ -38,6 +38,10 @@ module.exports = function (sequelize, DataTypes) {
type: DataTypes.TEXT,
},
+ tags: {
+ type: DataTypes.TEXT,
+ },
+
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
@@ -52,6 +56,42 @@ module.exports = function (sequelize, DataTypes) {
);
readings.associate = (db) => {
+ db.readings.belongsToMany(db.verses, {
+ as: 'versesref',
+ foreignKey: {
+ name: 'readings_versesrefId',
+ },
+ constraints: false,
+ through: 'readingsVersesrefVerses',
+ });
+
+ db.readings.belongsToMany(db.verses, {
+ as: 'versesref_filter',
+ foreignKey: {
+ name: 'readings_versesrefId',
+ },
+ constraints: false,
+ through: 'readingsVersesrefVerses',
+ });
+
+ db.readings.belongsToMany(db.verses, {
+ as: 'verses_select',
+ foreignKey: {
+ name: 'readings_verses_selectId',
+ },
+ constraints: false,
+ through: 'readingsVerses_selectVerses',
+ });
+
+ db.readings.belongsToMany(db.verses, {
+ as: 'verses_select_filter',
+ foreignKey: {
+ name: 'readings_verses_selectId',
+ },
+ constraints: false,
+ through: 'readingsVerses_selectVerses',
+ });
+
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.readings.hasMany(db.notes, {
@@ -72,6 +112,54 @@ module.exports = function (sequelize, DataTypes) {
constraints: false,
});
+ db.readings.belongsTo(db.books, {
+ as: 'bookref',
+ foreignKey: {
+ name: 'bookrefId',
+ },
+ constraints: false,
+ });
+
+ db.readings.belongsTo(db.chapters, {
+ as: 'chapterref',
+ foreignKey: {
+ name: 'chapterrefId',
+ },
+ constraints: false,
+ });
+
+ db.readings.belongsTo(db.translations, {
+ as: 'translationref',
+ foreignKey: {
+ name: 'translationrefId',
+ },
+ constraints: false,
+ });
+
+ db.readings.belongsTo(db.books, {
+ as: 'book_select',
+ foreignKey: {
+ name: 'book_selectId',
+ },
+ constraints: false,
+ });
+
+ db.readings.belongsTo(db.chapters, {
+ as: 'chapter_select',
+ foreignKey: {
+ name: 'chapter_selectId',
+ },
+ constraints: false,
+ });
+
+ db.readings.belongsTo(db.translations, {
+ as: 'translation_select',
+ foreignKey: {
+ name: 'translation_selectId',
+ },
+ constraints: false,
+ });
+
db.readings.belongsTo(db.users, {
as: 'createdBy',
});
diff --git a/backend/src/db/models/translations.js b/backend/src/db/models/translations.js
new file mode 100644
index 0000000..7ee1820
--- /dev/null
+++ b/backend/src/db/models/translations.js
@@ -0,0 +1,65 @@
+const config = require('../../config');
+const providers = config.providers;
+const crypto = require('crypto');
+const bcrypt = require('bcrypt');
+const moment = require('moment');
+
+module.exports = function (sequelize, DataTypes) {
+ const translations = sequelize.define(
+ 'translations',
+ {
+ id: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+
+ name: {
+ type: DataTypes.TEXT,
+ },
+
+ importHash: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ {
+ timestamps: true,
+ paranoid: true,
+ freezeTableName: true,
+ },
+ );
+
+ translations.associate = (db) => {
+ /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
+
+ db.translations.hasMany(db.readings, {
+ as: 'readings_translationref',
+ foreignKey: {
+ name: 'translationrefId',
+ },
+ constraints: false,
+ });
+
+ db.translations.hasMany(db.readings, {
+ as: 'readings_translation_select',
+ foreignKey: {
+ name: 'translation_selectId',
+ },
+ constraints: false,
+ });
+
+ //end loop
+
+ db.translations.belongsTo(db.users, {
+ as: 'createdBy',
+ });
+
+ db.translations.belongsTo(db.users, {
+ as: 'updatedBy',
+ });
+ };
+
+ return translations;
+};
diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js
index 8c3540b..0239f45 100644
--- a/backend/src/db/seeders/20231127130745-sample-data.js
+++ b/backend/src/db/seeders/20231127130745-sample-data.js
@@ -7,6 +7,14 @@ const Progress = db.progress;
const Readings = db.readings;
+const Books = db.books;
+
+const Chapters = db.chapters;
+
+const Verses = db.verses;
+
+const Translations = db.translations;
+
const NotesData = [
{
content: 'Reflecting on the creation story.',
@@ -37,6 +45,16 @@ const NotesData = [
// type code here for "relation_one" field
},
+
+ {
+ content: 'Blessings of the kingdom.',
+
+ tags: 'Beatitudes,blessings',
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+ },
];
const ProgressData = [
@@ -69,6 +87,16 @@ const ProgressData = [
// type code here for "relation_one" field
},
+
+ {
+ book: 'Matthew',
+
+ chapter: 5,
+
+ read: true,
+
+ // type code here for "relation_one" field
+ },
];
const ReadingsData = [
@@ -86,6 +114,24 @@ const ReadingsData = [
notes: 'In the beginning, God created the heavens and the earth.',
// type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_many" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_many" field
+
+ // type code here for "relation_one" field
+
+ tags: 'Max Planck',
},
{
@@ -102,6 +148,24 @@ const ReadingsData = [
notes: 'Moses and the burning bush.',
// type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_many" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_many" field
+
+ // type code here for "relation_one" field
+
+ tags: 'Francis Galton',
},
{
@@ -118,6 +182,138 @@ const ReadingsData = [
notes: 'The Lord is my shepherd.',
// type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_many" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_many" field
+
+ // type code here for "relation_one" field
+
+ tags: 'Comte de Buffon',
+ },
+
+ {
+ date: new Date('2023-10-04T08:00:00Z'),
+
+ book: 'Matthew',
+
+ chapter: 5,
+
+ verses: '1-12',
+
+ translation: 'NKJV',
+
+ notes: 'The Beatitudes.',
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_many" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_one" field
+
+ // type code here for "relation_many" field
+
+ // type code here for "relation_one" field
+
+ tags: 'Albert Einstein',
+ },
+];
+
+const BooksData = [
+ {
+ name: 'Linus Pauling',
+ },
+
+ {
+ name: 'Marie Curie',
+ },
+
+ {
+ name: 'Alfred Binet',
+ },
+
+ {
+ name: 'Konrad Lorenz',
+ },
+];
+
+const ChaptersData = [
+ {
+ number: 4,
+
+ // type code here for "relation_one" field
+ },
+
+ {
+ number: 3,
+
+ // type code here for "relation_one" field
+ },
+
+ {
+ number: 9,
+
+ // type code here for "relation_one" field
+ },
+
+ {
+ number: 1,
+
+ // type code here for "relation_one" field
+ },
+];
+
+const VersesData = [
+ {
+ number: 2,
+ },
+
+ {
+ number: 3,
+ },
+
+ {
+ number: 5,
+ },
+
+ {
+ number: 5,
+ },
+];
+
+const TranslationsData = [
+ {
+ name: 'Konrad Lorenz',
+ },
+
+ {
+ name: 'Enrico Fermi',
+ },
+
+ {
+ name: 'Paul Ehrlich',
+ },
+
+ {
+ name: 'Sheldon Glashow',
},
];
@@ -156,6 +352,17 @@ async function associateNoteWithReading() {
if (Note2?.setReading) {
await Note2.setReading(relatedReading2);
}
+
+ const relatedReading3 = await Readings.findOne({
+ offset: Math.floor(Math.random() * (await Readings.count())),
+ });
+ const Note3 = await Notes.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Note3?.setReading) {
+ await Note3.setReading(relatedReading3);
+ }
}
async function associateNoteWithUser() {
@@ -191,6 +398,17 @@ async function associateNoteWithUser() {
if (Note2?.setUser) {
await Note2.setUser(relatedUser2);
}
+
+ const relatedUser3 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Note3 = await Notes.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Note3?.setUser) {
+ await Note3.setUser(relatedUser3);
+ }
}
async function associateProgressWithUser() {
@@ -226,6 +444,17 @@ async function associateProgressWithUser() {
if (Progress2?.setUser) {
await Progress2.setUser(relatedUser2);
}
+
+ const relatedUser3 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Progress3 = await Progress.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Progress3?.setUser) {
+ await Progress3.setUser(relatedUser3);
+ }
}
async function associateReadingWithUser() {
@@ -261,6 +490,343 @@ async function associateReadingWithUser() {
if (Reading2?.setUser) {
await Reading2.setUser(relatedUser2);
}
+
+ const relatedUser3 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Reading3 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Reading3?.setUser) {
+ await Reading3.setUser(relatedUser3);
+ }
+}
+
+async function associateReadingWithBookref() {
+ const relatedBookref0 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Reading0 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Reading0?.setBookref) {
+ await Reading0.setBookref(relatedBookref0);
+ }
+
+ const relatedBookref1 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Reading1 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Reading1?.setBookref) {
+ await Reading1.setBookref(relatedBookref1);
+ }
+
+ const relatedBookref2 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Reading2 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Reading2?.setBookref) {
+ await Reading2.setBookref(relatedBookref2);
+ }
+
+ const relatedBookref3 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Reading3 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Reading3?.setBookref) {
+ await Reading3.setBookref(relatedBookref3);
+ }
+}
+
+async function associateReadingWithChapterref() {
+ const relatedChapterref0 = await Chapters.findOne({
+ offset: Math.floor(Math.random() * (await Chapters.count())),
+ });
+ const Reading0 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Reading0?.setChapterref) {
+ await Reading0.setChapterref(relatedChapterref0);
+ }
+
+ const relatedChapterref1 = await Chapters.findOne({
+ offset: Math.floor(Math.random() * (await Chapters.count())),
+ });
+ const Reading1 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Reading1?.setChapterref) {
+ await Reading1.setChapterref(relatedChapterref1);
+ }
+
+ const relatedChapterref2 = await Chapters.findOne({
+ offset: Math.floor(Math.random() * (await Chapters.count())),
+ });
+ const Reading2 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Reading2?.setChapterref) {
+ await Reading2.setChapterref(relatedChapterref2);
+ }
+
+ const relatedChapterref3 = await Chapters.findOne({
+ offset: Math.floor(Math.random() * (await Chapters.count())),
+ });
+ const Reading3 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Reading3?.setChapterref) {
+ await Reading3.setChapterref(relatedChapterref3);
+ }
+}
+
+// Similar logic for "relation_many"
+
+async function associateReadingWithTranslationref() {
+ const relatedTranslationref0 = await Translations.findOne({
+ offset: Math.floor(Math.random() * (await Translations.count())),
+ });
+ const Reading0 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Reading0?.setTranslationref) {
+ await Reading0.setTranslationref(relatedTranslationref0);
+ }
+
+ const relatedTranslationref1 = await Translations.findOne({
+ offset: Math.floor(Math.random() * (await Translations.count())),
+ });
+ const Reading1 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Reading1?.setTranslationref) {
+ await Reading1.setTranslationref(relatedTranslationref1);
+ }
+
+ const relatedTranslationref2 = await Translations.findOne({
+ offset: Math.floor(Math.random() * (await Translations.count())),
+ });
+ const Reading2 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Reading2?.setTranslationref) {
+ await Reading2.setTranslationref(relatedTranslationref2);
+ }
+
+ const relatedTranslationref3 = await Translations.findOne({
+ offset: Math.floor(Math.random() * (await Translations.count())),
+ });
+ const Reading3 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Reading3?.setTranslationref) {
+ await Reading3.setTranslationref(relatedTranslationref3);
+ }
+}
+
+async function associateReadingWithBook_select() {
+ const relatedBook_select0 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Reading0 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Reading0?.setBook_select) {
+ await Reading0.setBook_select(relatedBook_select0);
+ }
+
+ const relatedBook_select1 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Reading1 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Reading1?.setBook_select) {
+ await Reading1.setBook_select(relatedBook_select1);
+ }
+
+ const relatedBook_select2 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Reading2 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Reading2?.setBook_select) {
+ await Reading2.setBook_select(relatedBook_select2);
+ }
+
+ const relatedBook_select3 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Reading3 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Reading3?.setBook_select) {
+ await Reading3.setBook_select(relatedBook_select3);
+ }
+}
+
+async function associateReadingWithChapter_select() {
+ const relatedChapter_select0 = await Chapters.findOne({
+ offset: Math.floor(Math.random() * (await Chapters.count())),
+ });
+ const Reading0 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Reading0?.setChapter_select) {
+ await Reading0.setChapter_select(relatedChapter_select0);
+ }
+
+ const relatedChapter_select1 = await Chapters.findOne({
+ offset: Math.floor(Math.random() * (await Chapters.count())),
+ });
+ const Reading1 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Reading1?.setChapter_select) {
+ await Reading1.setChapter_select(relatedChapter_select1);
+ }
+
+ const relatedChapter_select2 = await Chapters.findOne({
+ offset: Math.floor(Math.random() * (await Chapters.count())),
+ });
+ const Reading2 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Reading2?.setChapter_select) {
+ await Reading2.setChapter_select(relatedChapter_select2);
+ }
+
+ const relatedChapter_select3 = await Chapters.findOne({
+ offset: Math.floor(Math.random() * (await Chapters.count())),
+ });
+ const Reading3 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Reading3?.setChapter_select) {
+ await Reading3.setChapter_select(relatedChapter_select3);
+ }
+}
+
+// Similar logic for "relation_many"
+
+async function associateReadingWithTranslation_select() {
+ const relatedTranslation_select0 = await Translations.findOne({
+ offset: Math.floor(Math.random() * (await Translations.count())),
+ });
+ const Reading0 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Reading0?.setTranslation_select) {
+ await Reading0.setTranslation_select(relatedTranslation_select0);
+ }
+
+ const relatedTranslation_select1 = await Translations.findOne({
+ offset: Math.floor(Math.random() * (await Translations.count())),
+ });
+ const Reading1 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Reading1?.setTranslation_select) {
+ await Reading1.setTranslation_select(relatedTranslation_select1);
+ }
+
+ const relatedTranslation_select2 = await Translations.findOne({
+ offset: Math.floor(Math.random() * (await Translations.count())),
+ });
+ const Reading2 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Reading2?.setTranslation_select) {
+ await Reading2.setTranslation_select(relatedTranslation_select2);
+ }
+
+ const relatedTranslation_select3 = await Translations.findOne({
+ offset: Math.floor(Math.random() * (await Translations.count())),
+ });
+ const Reading3 = await Readings.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Reading3?.setTranslation_select) {
+ await Reading3.setTranslation_select(relatedTranslation_select3);
+ }
+}
+
+async function associateChapterWithBook() {
+ const relatedBook0 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Chapter0 = await Chapters.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Chapter0?.setBook) {
+ await Chapter0.setBook(relatedBook0);
+ }
+
+ const relatedBook1 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Chapter1 = await Chapters.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Chapter1?.setBook) {
+ await Chapter1.setBook(relatedBook1);
+ }
+
+ const relatedBook2 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Chapter2 = await Chapters.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Chapter2?.setBook) {
+ await Chapter2.setBook(relatedBook2);
+ }
+
+ const relatedBook3 = await Books.findOne({
+ offset: Math.floor(Math.random() * (await Books.count())),
+ });
+ const Chapter3 = await Chapters.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Chapter3?.setBook) {
+ await Chapter3.setBook(relatedBook3);
+ }
}
module.exports = {
@@ -271,6 +837,14 @@ module.exports = {
await Readings.bulkCreate(ReadingsData);
+ await Books.bulkCreate(BooksData);
+
+ await Chapters.bulkCreate(ChaptersData);
+
+ await Verses.bulkCreate(VersesData);
+
+ await Translations.bulkCreate(TranslationsData);
+
await Promise.all([
// Similar logic for "relation_many"
@@ -281,6 +855,24 @@ module.exports = {
await associateProgressWithUser(),
await associateReadingWithUser(),
+
+ await associateReadingWithBookref(),
+
+ await associateReadingWithChapterref(),
+
+ // Similar logic for "relation_many"
+
+ await associateReadingWithTranslationref(),
+
+ await associateReadingWithBook_select(),
+
+ await associateReadingWithChapter_select(),
+
+ // Similar logic for "relation_many"
+
+ await associateReadingWithTranslation_select(),
+
+ await associateChapterWithBook(),
]);
},
@@ -290,5 +882,13 @@ module.exports = {
await queryInterface.bulkDelete('progress', null, {});
await queryInterface.bulkDelete('readings', null, {});
+
+ await queryInterface.bulkDelete('books', null, {});
+
+ await queryInterface.bulkDelete('chapters', null, {});
+
+ await queryInterface.bulkDelete('verses', null, {});
+
+ await queryInterface.bulkDelete('translations', null, {});
},
};
diff --git a/backend/src/routes/readings.js b/backend/src/routes/readings.js
index bd7f9e7..55528b8 100644
--- a/backend/src/routes/readings.js
+++ b/backend/src/routes/readings.js
@@ -32,6 +32,9 @@ router.use(checkCrudPermissions('readings'));
* notes:
* type: string
* default: notes
+ * tags:
+ * type: string
+ * default: tags
* chapter:
* type: integer
@@ -323,6 +326,7 @@ router.get(
'verses',
'translation',
'notes',
+ 'tags',
'chapter',
'date',
diff --git a/backend/src/services/search.js b/backend/src/services/search.js
index 09866ea..ecedb7e 100644
--- a/backend/src/services/search.js
+++ b/backend/src/services/search.js
@@ -47,12 +47,20 @@ module.exports = class SearchService {
progress: ['book'],
- readings: ['book', 'verses', 'translation', 'notes'],
+ readings: ['book', 'verses', 'translation', 'notes', 'tags'],
+
+ books: ['name'],
+
+ translations: ['name'],
};
const columnsInt = {
progress: ['chapter'],
readings: ['chapter'],
+
+ chapters: ['number'],
+
+ verses: ['number'],
};
let allFoundRecords = [];
diff --git a/frontend/src/components/Readings/CardReadings.tsx b/frontend/src/components/Readings/CardReadings.tsx
index cdcb169..f75ec6a 100644
--- a/frontend/src/components/Readings/CardReadings.tsx
+++ b/frontend/src/components/Readings/CardReadings.tsx
@@ -62,7 +62,7 @@ const CardReadings = ({
href={`/readings/readings-view/?id=${item.id}`}
className='text-lg font-bold leading-6 line-clamp-1'
>
- {item.book}
+ {item.tags}
@@ -140,6 +140,111 @@ const CardReadings = ({
+
+
+
+ Bookref
+
+
+
+ {dataFormatter.booksOneListFormatter(item.bookref)}
+
+
+
+
+
+
+ Chapterref
+
+
+
+ {dataFormatter.chaptersOneListFormatter(item.chapterref)}
+
+
+
+
+
+
+ Versesref
+
+
+
+ {dataFormatter
+ .versesManyListFormatter(item.versesref)
+ .join(', ')}
+
+
+
+
+
+
+ Translationref
+
+
+
+ {dataFormatter.translationsOneListFormatter(
+ item.translationref,
+ )}
+
+
+
+
+
+
+ Book_select
+
+
+
+ {dataFormatter.booksOneListFormatter(item.book_select)}
+
+
+
+
+
+
+ Chapter_select
+
+
+
+ {dataFormatter.chaptersOneListFormatter(
+ item.chapter_select,
+ )}
+
+
+
+
+
+
+ Verses_select
+
+
+
+ {dataFormatter
+ .versesManyListFormatter(item.verses_select)
+ .join(', ')}
+
+
+
+
+
+
+ Translation_select
+
+
+
+ {dataFormatter.translationsOneListFormatter(
+ item.translation_select,
+ )}
+
+
+
+
+
+
Tags
+
+ {item.tags}
+
+
))}
diff --git a/frontend/src/components/Readings/ListReadings.tsx b/frontend/src/components/Readings/ListReadings.tsx
index abe7d14..660b254 100644
--- a/frontend/src/components/Readings/ListReadings.tsx
+++ b/frontend/src/components/Readings/ListReadings.tsx
@@ -89,6 +89,87 @@ const ListReadings = ({
{dataFormatter.usersOneListFormatter(item.user)}
+
+
+
Bookref
+
+ {dataFormatter.booksOneListFormatter(item.bookref)}
+
+
+
+
+
Chapterref
+
+ {dataFormatter.chaptersOneListFormatter(
+ item.chapterref,
+ )}
+
+
+
+
+
Versesref
+
+ {dataFormatter
+ .versesManyListFormatter(item.versesref)
+ .join(', ')}
+
+
+
+
+
+ Translationref
+
+
+ {dataFormatter.translationsOneListFormatter(
+ item.translationref,
+ )}
+
+
+
+
+
Book_select
+
+ {dataFormatter.booksOneListFormatter(item.book_select)}
+
+
+
+
+
+ Chapter_select
+
+
+ {dataFormatter.chaptersOneListFormatter(
+ item.chapter_select,
+ )}
+
+
+
+
+
+ Verses_select
+
+
+ {dataFormatter
+ .versesManyListFormatter(item.verses_select)
+ .join(', ')}
+
+
+
+
+
+ Translation_select
+
+
+ {dataFormatter.translationsOneListFormatter(
+ item.translation_select,
+ )}
+
+
+
+
value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('books'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'chapterref',
+ headerName: 'Chapterref',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ sortable: false,
+ type: 'singleSelect',
+ getOptionValue: (value: any) => value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('chapters'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'versesref',
+ headerName: 'Versesref',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: false,
+ sortable: false,
+ type: 'singleSelect',
+ valueFormatter: ({ value }) =>
+ dataFormatter.versesManyListFormatter(value).join(', '),
+ renderEditCell: (params) => (
+
+ ),
+ },
+
+ {
+ field: 'translationref',
+ headerName: 'Translationref',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ sortable: false,
+ type: 'singleSelect',
+ getOptionValue: (value: any) => value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('translations'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'book_select',
+ headerName: 'Book_select',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ sortable: false,
+ type: 'singleSelect',
+ getOptionValue: (value: any) => value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('books'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'chapter_select',
+ headerName: 'Chapter_select',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ sortable: false,
+ type: 'singleSelect',
+ getOptionValue: (value: any) => value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('chapters'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'verses_select',
+ headerName: 'Verses_select',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: false,
+ sortable: false,
+ type: 'singleSelect',
+ valueFormatter: ({ value }) =>
+ dataFormatter.versesManyListFormatter(value).join(', '),
+ renderEditCell: (params) => (
+
+ ),
+ },
+
+ {
+ field: 'translation_select',
+ headerName: 'Translation_select',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ sortable: false,
+ type: 'singleSelect',
+ getOptionValue: (value: any) => value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('translations'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'tags',
+ headerName: 'Tags',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+ },
+
{
field: 'actions',
type: 'actions',
diff --git a/frontend/src/components/WebPageComponents/Footer.tsx b/frontend/src/components/WebPageComponents/Footer.tsx
index dde7f9e..07d5aa8 100644
--- a/frontend/src/components/WebPageComponents/Footer.tsx
+++ b/frontend/src/components/WebPageComponents/Footer.tsx
@@ -17,9 +17,9 @@ export default function WebSiteFooter({ projectName }: WebSiteFooterProps) {
const borders = useAppSelector((state) => state.style.borders);
const websiteHeder = useAppSelector((state) => state.style.websiteHeder);
- const style = FooterStyle.WITH_PROJECT_NAME;
+ const style = FooterStyle.WITH_PAGES;
- const design = FooterDesigns.DEFAULT_DESIGN;
+ const design = FooterDesigns.DESIGN_DIVERSITY;
return (
state.style.websiteHeder);
const borders = useAppSelector((state) => state.style.borders);
- const style = HeaderStyle.PAGES_RIGHT;
+ const style = HeaderStyle.PAGES_LEFT;
- const design = HeaderDesigns.DESIGN_DIVERSITY;
+ const design = HeaderDesigns.DEFAULT_DESIGN;
return (