Daniel Sheffield 9 months ago
parent
commit
4f3421db7c
9 changed files with 236 additions and 367 deletions
  1. 0 12
      sqlpage/apply.sql
  2. 47 42
      sqlpage/categories.sql
  3. 3 1
      sqlpage/clear.sql
  4. 13 0
      sqlpage/cookie.sql
  5. 0 115
      sqlpage/filter.sql
  6. 47 42
      sqlpage/groups.sql
  7. 75 113
      sqlpage/nav.sql
  8. 47 42
      sqlpage/products.sql
  9. 4 0
      sqlpage/theme.sql

+ 0 - 12
sqlpage/apply.sql

@@ -1,12 +0,0 @@
-SELECT
-  'dynamic' as component,
-  sqlpage.run_sql('filter.sql') AS properties
-;
-
-SELECT
-  'redirect' AS component,
-  (SELECT CASE
-    WHEN $path IN ('/products.sql', '/categories.sql', '/groups.sql', '/tags.sql')
-    THEN $path ELSE '/transactions.sql'
-    END
-  ) AS link;

+ 47 - 42
sqlpage/categories.sql

@@ -1,59 +1,64 @@
-SELECT
-  'dynamic' as component,
-  sqlpage.run_sql('cookie.sql') AS properties
-;
-
-SELECT 'shell' AS component,
-  'dark' AS theme,
-  'Categories' AS title
-;
-
-SELECT
-  'dynamic' as component,
-  sqlpage.run_sql('nav.sql') AS properties
-;
-
-SELECT
-  'table' AS component,
-  TRUE AS search,
-  TRUE AS striped_rows,
-  TRUE AS small,
-  'Notice' AS markdown
-;
+SET title = 'Categories';
+SET products = COALESCE($products, sqlpage.cookie('products'));
 
 DROP TABLE IF EXISTS sqlpage_categories;
 CREATE TEMPORARY TABLE IF NOT EXISTS sqlpage_categories AS (
 SELECT
-    "Products",
-    COALESCE("category", "Categories"||'') "Category",
-    COALESCE("group", "Groups"||'') "Group"
-FROM (SELECT
   count(DISTINCT p.id) AS "Products",
   count(DISTINCT c.id) AS "Categories",
   count(DISTINCT g.id) AS "Groups",
-  p.name AS "product",
-  c.name AS "category",
+  p.name AS product,
+  c.name AS category,
   g.name AS "group"
 FROM products p
 JOIN categories c ON p.category_id = c.id
 JOIN groups g ON c.group_id = g.id
-JOIN sqlpage_options op ON op.type = 'product' AND p.name = op.name
-JOIN sqlpage_options oc ON oc.type = 'category' AND c.name = oc.name
-JOIN sqlpage_options og ON og.type = 'group' AND g.name = og.name
 WHERE
-  COALESCE(op.selected, oc.selected, og.selected, TRUE)
+  (p.name IN (SELECT v#>>'{}' FROM json_array_elements($products::json) j(v)) OR $products IS NULL)
 AND
-  op.session = sqlpage.cookie('session')
+  (c.name IN (SELECT v#>>'{}' FROM json_array_elements($categories::json) j(v)) OR $categories IS NULL)
 AND
-  oc.session = sqlpage.cookie('session')
-AND
-  og.session = sqlpage.cookie('session')
-GROUP BY ROLLUP (g.name, c.name, p.name)
-ORDER BY product, category, "group") q
-WHERE q.product IS NULL AND (q.category IS NOT NULL OR q.group IS NULL)
+  (g.name IN (SELECT v#>>'{}' FROM json_array_elements($groups::json) j(v)) OR $groups IS NULL)
+GROUP BY ROLLUP ("group", category, product)
 );
-SELECT * FROM sqlpage_categories;
 
-SELECT 
-  '# No Data' AS "Notice"
+SET product_options = (
+SELECT
+  json_agg(json_build_object(
+    'label', value,
+    'value', value,
+    'selected', value IN (SELECT v#>>'{}' FROM json_array_elements($products::json) j(v))
+  ) ORDER BY value)
+FROM (
+  SELECT v#>>'{}'
+  FROM json_array_elements($products::json) j(v)
+  UNION
+  SELECT product
+  FROM sqlpage_categories
+) o(value)
+WHERE value IS NOT NULL
+);
+
+SELECT 'dynamic' AS component, sqlpage.run_sql('cookie.sql') AS properties;
+SELECT 'dynamic' AS component, sqlpage.run_sql('theme.sql') AS properties;
+SELECT 'dynamic' AS component, sqlpage.run_sql('nav.sql') AS properties;
+
+SELECT
+  'table' AS component,
+  TRUE AS search,
+  TRUE AS striped_rows,
+  TRUE AS small,
+  'Notice' AS markdown
+;
+
+SELECT
+  "Products",
+  COALESCE("category", "Categories"||'') "Category",
+  COALESCE("group", "Groups"||'') "Group"
+FROM sqlpage_categories q
+WHERE q.product IS NULL AND (q.category IS NOT NULL OR q.group IS NULL)
+ORDER BY product, category, "group"
+;
+
+SELECT '# No Data' AS "Notice"
 WHERE NOT EXISTS(SELECT * FROM sqlpage_categories LIMIT 1);

+ 3 - 1
sqlpage/clear.sql

@@ -1,6 +1,8 @@
+SET products = NULL;
+SELECT 'dynamic' AS component, sqlpage.run_sql('cookie.sql') AS properties;
 SELECT
   'redirect' AS component,
-  'apply.sql?path='||(SELECT CASE
+  (SELECT CASE
     WHEN $path IN ('/products.sql', '/categories.sql', '/groups.sql', '/tags.sql')
     THEN $path ELSE '/transactions.sql'
     END

+ 13 - 0
sqlpage/cookie.sql

@@ -5,3 +5,16 @@ SELECT
 WHERE sqlpage.cookie('session') IS NULL
 ;
 
+SELECT
+  'cookie' AS component,
+  'products' AS name,
+  $products AS value
+WHERE $products IS NOT NULL
+;
+SELECT
+  'cookie' AS component,
+  'products' AS name,
+  $products AS value,
+  TRUE AS remove
+WHERE $products IS NULL
+;

+ 0 - 115
sqlpage/filter.sql

@@ -1,115 +0,0 @@
-CREATE TABLE IF NOT EXISTS sqlpage_options(
-  session text,
-  "type" text,
-  name text,
-  selected boolean
-);
-DELETE FROM sqlpage_options
-WHERE
-  session = sqlpage.cookie('session')
-
-INSERT INTO sqlpage_options(session, type, name, selected)
-VALUES (
-  sqlpage.cookie('session'),
-  'start',
-  $start,
-  TRUE
-), (
-  sqlpage.cookie('session'),
-  'end',
-  $end,
-  TRUE
-)
-;
-
-INSERT INTO sqlpage_options
-SELECT
-  sqlpage.cookie('session'),
-  CASE
-  WHEN ut.name = 'Volume' THEN 'unit_volume' 
-  WHEN ut.name = 'Mass' THEN 'unit_mass' 
-  WHEN ut.name = 'Count' THEN 'unit_count' 
-  END,
-  u.name,
-  CASE
-  WHEN ut.name = 'Volume' THEN u.name = COALESCE($unit_volume, 'L')
-  WHEN ut.name = 'Mass' THEN u.name = COALESCE($unit_mass, 'kg')
-  WHEN ut.name = 'Count' THEN CASE WHEN $unit_count IS NOT NULL THEN f.v IS NOT NULL ELSE NULL END
-  END
-FROM units u
-JOIN unit_types ut
-ON ut.id = u.unit_type_id
-LEFT JOIN json_array_elements($unit_count::json) f(v)
-ON u.name = f.v#>>'{}' AND ut.name = 'Count'
-;
-
-INSERT INTO sqlpage_options
-SELECT
-  sqlpage.cookie('session'),
-  'tag',
-  t.name,
-  CASE WHEN $tags IS NOT NULL THEN f.v IS NOT NULL ELSE NULL END
-FROM tags t
-LEFT JOIN json_array_elements($tags::json) f(v)
-ON t.name = f.v#>>'{}'
-;
-
-INSERT INTO sqlpage_options
-SELECT
-  sqlpage.cookie('session'),
-  UNNEST(ARRAY['product', 'category', 'group']) AS "type",
-  UNNEST(q.name) AS name,
-  UNNEST(selected)
-FROM (
-  SELECT
-    ARRAY[p.name, c.name, g.name] AS name,
-    ARRAY[
-      CASE WHEN $products IS NOT NULL THEN fp.v IS NOT NULL ELSE NULL END,
-      CASE WHEN $categories IS NOT NULL THEN fc.v IS NOT NULL ELSE NULL END,
-      CASE WHEN $groups IS NOT NULL THEN fg.v IS NOT NULL ELSE NULL END
-    ]
-  FROM products p
-  LEFT JOIN json_array_elements($products::json) fp(v)
-  ON p.name = fp.v#>>'{}'
-  JOIN categories c ON p.category_id = c.id
-  LEFT JOIN json_array_elements($categories::json) fc(v)
-  ON c.name = fc.v#>>'{}'
-  JOIN groups g ON c.group_id = g.id
-  LEFT JOIN json_array_elements($groups::json) fg(v)
-  ON g.name = fg.v#>>'{}'
-  WHERE (
-    (p.name = fp.v#>>'{}' OR $products IS NULL)
-  AND
-    (c.name = fc.v#>>'{}' OR $categories IS NULL)
-  AND
-    (g.name = fg.v#>>'{}' OR $groups IS NULL)
-  )
-) q(name, selected)
-UNION
-SELECT
-  sqlpage.cookie('session'),
-  'group',
-  f.v#>>'{}',
-  TRUE
-FROM json_array_elements($groups::json) f(v)
-LEFT JOIN groups g ON g.name = f.v#>>'{}'
-WHERE g.name is NULL
-UNION
-SELECT
-  sqlpage.cookie('session'),
-  'category',
-  f.v#>>'{}',
-  TRUE
-FROM json_array_elements($categories::json) f(v)
-LEFT JOIN categories c ON c.name = f.v#>>'{}'
-WHERE c.name is NULL
-UNION
-SELECT
-  sqlpage.cookie('session'),
-  'product',
-  f.v#>>'{}',
-  TRUE
-FROM json_array_elements($products::json) f(v)
-LEFT JOIN products p ON p.name = f.v#>>'{}'
-WHERE p.name is NULL
-;

+ 47 - 42
sqlpage/groups.sql

@@ -1,59 +1,64 @@
-SELECT
-  'dynamic' as component,
-  sqlpage.run_sql('cookie.sql') AS properties
-;
-
-SELECT 'shell' AS component,
-  'dark' AS theme,
-  'Groups' AS title
-;
-
-SELECT
-  'dynamic' as component,
-  sqlpage.run_sql('nav.sql') AS properties
-;
-
-SELECT
-  'table' AS component,
-  TRUE AS search,
-  TRUE AS striped_rows,
-  TRUE AS small,
-  'Notice' AS markdown
-;
+SET title = 'Groups';
+SET products = COALESCE($products, sqlpage.cookie('products'));
 
 DROP TABLE IF EXISTS sqlpage_groups;
 CREATE TEMPORARY TABLE IF NOT EXISTS sqlpage_groups AS (
 SELECT
-    "Products",
-    "Categories",
-    COALESCE("group", "Groups"||'') "Group"
-FROM (SELECT
   count(DISTINCT p.id) AS "Products",
   count(DISTINCT c.id) AS "Categories",
   count(DISTINCT g.id) AS "Groups",
-  p.name AS "product",
-  c.name AS "category",
+  p.name AS product,
+  c.name AS category,
   g.name AS "group"
 FROM products p
 JOIN categories c ON p.category_id = c.id
 JOIN groups g ON c.group_id = g.id
-JOIN sqlpage_options op ON op.type = 'product' AND p.name = op.name
-JOIN sqlpage_options oc ON oc.type = 'category' AND c.name = oc.name
-JOIN sqlpage_options og ON og.type = 'group' AND g.name = og.name
 WHERE
-  COALESCE(op.selected, oc.selected, og.selected, TRUE)
+  (p.name IN (SELECT v#>>'{}' FROM json_array_elements($products::json) j(v)) OR $products IS NULL)
 AND
-  op.session = sqlpage.cookie('session')
+  (c.name IN (SELECT v#>>'{}' FROM json_array_elements($categories::json) j(v)) OR $categories IS NULL)
 AND
-  oc.session = sqlpage.cookie('session')
-AND
-  og.session = sqlpage.cookie('session')
-GROUP BY ROLLUP (g.name, c.name, p.name)
-ORDER BY product, category, "group") q
-WHERE q.category IS NULL
+  (g.name IN (SELECT v#>>'{}' FROM json_array_elements($groups::json) j(v)) OR $groups IS NULL)
+GROUP BY ROLLUP ("group", category, product)
 );
-SELECT * FROM sqlpage_groups;
 
-SELECT 
-  '# No Data' AS "Notice"
+SET product_options = (
+SELECT
+  json_agg(json_build_object(
+    'label', value,
+    'value', value,
+    'selected', value IN (SELECT v#>>'{}' FROM json_array_elements($products::json) j(v))
+  ) ORDER BY value)
+FROM (
+  SELECT v#>>'{}'
+  FROM json_array_elements($products::json) j(v)
+  UNION
+  SELECT product
+  FROM sqlpage_groups
+) o(value)
+WHERE value IS NOT NULL
+);
+
+SELECT 'dynamic' AS component, sqlpage.run_sql('cookie.sql') AS properties;
+SELECT 'dynamic' AS component, sqlpage.run_sql('theme.sql') AS properties;
+SELECT 'dynamic' AS component, sqlpage.run_sql('nav.sql') AS properties;
+
+SELECT
+  'table' AS component,
+  TRUE AS search,
+  TRUE AS striped_rows,
+  TRUE AS small,
+  'Notice' AS markdown
+;
+
+SELECT
+  "Products",
+  "Categories",
+  COALESCE("group", "Groups"||'') "Group"
+FROM sqlpage_groups q
+WHERE q.category IS NULL
+ORDER BY product, category, "group"
+;
+
+SELECT '# No Data' AS "Notice"
 WHERE NOT EXISTS(SELECT * FROM sqlpage_groups LIMIT 1);

+ 75 - 113
sqlpage/nav.sql

@@ -39,13 +39,11 @@ SELECT
   'filter' AS id,
   'get' AS method,
   '' AS title,
-  'apply.sql' AS action,
   'Apply' AS validate
 ;
 
 SELECT
   'path' AS name,
-  '' AS label,
   sqlpage.path() AS value,
   'hidden' AS type
 ;
@@ -54,70 +52,43 @@ SELECT
   'products[]' AS name,
   'Product' AS label,
   4 AS width,
-  'select' AS "type",
+  'select' AS type,
   TRUE AS dropdown,
   TRUE AS multiple,
-  json_agg(json_build_object(
-    'label', o.name,
-    'value', o.name,
-    'selected', COALESCE(o.selected, FALSE)
-  ) ORDER BY o.name) AS options
-FROM sqlpage_options o
-WHERE
-  o.type = 'product'
-AND
-  o.session = sqlpage.cookie('session')
+  $product_options AS options
 ;
 SELECT
   'categories[]' AS name,
   'Categories' AS label,
   4 AS width,
-  'select' AS "type",
+  'select' AS type,
   TRUE AS dropdown,
   TRUE AS multiple,
-  json_agg(json_build_object(
-    'label', o.name,
-    'value', o.name,
-    'selected', COALESCE(o.selected, FALSE)
-  ) ORDER BY o.name) AS options
-FROM sqlpage_options o
-WHERE
-  o.type = 'category'
-AND
-  o.session = sqlpage.cookie('session')
+  $category_options AS options
 ;
 SELECT
   'groups[]' AS name,
   'Groups' AS label,
   4 AS width,
-  'select' AS "type",
+  'select' AS type,
   TRUE AS dropdown,
   TRUE AS multiple,
-  json_agg(json_build_object(
-    'label', o.name,
-    'value', o.name,
-    'selected', COALESCE(o.selected, FALSE)
-  ) ORDER BY o.name) AS options
-FROM sqlpage_options o
-WHERE
-  o.type = 'group'
-AND
-  o.session = sqlpage.cookie('session')
-;
-SELECT
-  'start' AS name,
-  'From' AS label,
-  2 AS width,
-  (SELECT COALESCE((SELECT name FROM sqlpage_options WHERE type = 'start'), to_char(now()-'30 days'::interval, 'YYYY-MM-DD'))) AS value,
-  'date' AS type
-;
-SELECT
-  'end' AS name,
-  'To' AS label,
-  2 AS width,
-  (SELECT COALESCE((SELECT name FROM sqlpage_options WHERE type = 'end'), to_char(now(), 'YYYY-MM-DD'))) AS value,
-  'date' AS type
-;
+  $group_options AS options
+;
+--SELECT
+--  'start' AS name,
+--  'From' AS label,
+--  2 AS width,
+--  (SELECT COALESCE((SELECT name FROM sqlpage_options WHERE type = 'start'), to_char(now()-'30 days'::interval, 'YYYY-MM-DD'))) AS value,
+--  'date' AS type
+--;
+--SELECT
+--  'end' AS name,
+--  'To' AS label,
+--  2 AS width,
+--  (SELECT COALESCE((SELECT name FROM sqlpage_options WHERE type = 'end'), to_char(now(), 'YYYY-MM-DD'))) AS value,
+--  'date' AS type
+--;
 SELECT
   'tags[]' AS name,
   'Tags' AS label,
@@ -125,69 +96,60 @@ SELECT
   'select' AS "type",
   TRUE AS dropdown,
   TRUE AS multiple,
-  json_agg(json_build_object(
-    'label', o.name,
-    'value', o.name,
-    'selected', COALESCE(o.selected, FALSE)
-  ) ORDER BY o.name) AS options
-FROM sqlpage_options o
-WHERE
-  o.type = 'tag'
-AND
-  o.session = sqlpage.cookie('session')
-;
-SELECT
-  'unit_volume' AS name,
-  'Unit (vol.)' AS label,
-  1 AS width,
-  'select' AS type,
-  json_agg(json_build_object(
-    'label', o.name,
-    'value', o.name,
-    'selected', COALESCE(o.selected, FALSE)
-  ) ORDER BY o.name) AS options
-FROM sqlpage_options o
-WHERE
-  o.type = 'unit_volume'
-AND
-  o.session = sqlpage.cookie('session')
-;
-SELECT
-  'unit_mass' AS name,
-  'Unit (mass)' AS label,
-  1 AS width,
-  'select' AS type,
-  $unit_mass AS value,
-  json_agg(json_build_object(
-    'label', o.name,
-    'value', o.name,
-    'selected', COALESCE(o.selected, FALSE)
-  ) ORDER BY o.name) AS options
-FROM sqlpage_options o
-WHERE
-  o.type = 'unit_mass'
-AND
-  o.session = sqlpage.cookie('session')
-;
-SELECT
-  'unit_count[]' AS name,
-  'Unit (count)' AS label,
-  2 AS width,
-  'select' AS type,
-  TRUE AS multiple,
-  TRUE AS dropdown,
-  '' AS value,
-  json_agg(json_build_object(
-    'label', o.name,
-    'value', o.name,
-    'selected', COALESCE(o.selected, FALSE)
-  ) ORDER BY o.name) AS options
-FROM sqlpage_options o
-WHERE
-  o.type = 'unit_count'
-AND
-  o.session = sqlpage.cookie('session')
-;
+  $tag_options AS options
+;
+--SELECT
+--  'unit_volume' AS name,
+--  'Unit (vol.)' AS label,
+--  1 AS width,
+--  'select' AS type,
+--  json_agg(json_build_object(
+--    'label', o.name,
+--    'value', o.name,
+--    'selected', COALESCE(o.selected, FALSE)
+--  ) ORDER BY o.name) AS options
+--FROM sqlpage_options o
+--WHERE
+--  o.type = 'unit_volume'
+--AND
+--  o.session = sqlpage.cookie('session')
+--;
+--SELECT
+--  'unit_mass' AS name,
+--  'Unit (mass)' AS label,
+--  1 AS width,
+--  'select' AS type,
+--  $unit_mass AS value,
+--  json_agg(json_build_object(
+--    'label', o.name,
+--    'value', o.name,
+--    'selected', COALESCE(o.selected, FALSE)
+--  ) ORDER BY o.name) AS options
+--FROM sqlpage_options o
+--WHERE
+--  o.type = 'unit_mass'
+--AND
+--  o.session = sqlpage.cookie('session')
+--;
+--SELECT
+--  'unit_count[]' AS name,
+--  'Unit (count)' AS label,
+--  2 AS width,
+--  'select' AS type,
+--  TRUE AS multiple,
+--  TRUE AS dropdown,
+--  '' AS value,
+--  json_agg(json_build_object(
+--    'label', o.name,
+--    'value', o.name,
+--    'selected', COALESCE(o.selected, FALSE)
+--  ) ORDER BY o.name) AS options
+--FROM sqlpage_options o
+--WHERE
+--  o.type = 'unit_count'
+--AND
+--  o.session = sqlpage.cookie('session')
+--;
 SELECT
   'clear' AS name,
   'Clear' AS value,

+ 47 - 42
sqlpage/products.sql

@@ -1,59 +1,64 @@
-SELECT
-  'dynamic' as component,
-  sqlpage.run_sql('cookie.sql') AS properties
-;
-
-SELECT 'shell' AS component,
-  'dark' AS theme,
-  'Products' AS title
-;
-
-SELECT
-  'dynamic' as component,
-  sqlpage.run_sql('nav.sql') AS properties
-;
-
-SELECT
-  'table' AS component,
-  TRUE AS search,
-  TRUE AS striped_rows,
-  TRUE AS small,
-  'Notice' AS markdown
-;
+SET title = 'Products';
+SET products = COALESCE($products, sqlpage.cookie('products'));
 
 DROP TABLE IF EXISTS sqlpage_products;
 CREATE TEMPORARY TABLE IF NOT EXISTS sqlpage_products AS (
 SELECT
-    COALESCE("product", "Products"||'') "Product",
-    COALESCE("category", "Categories"||'') "Category",
-    COALESCE("group", "Groups"||'') "Group"
-FROM (SELECT
   count(DISTINCT p.id) AS "Products",
   count(DISTINCT c.id) AS "Categories",
   count(DISTINCT g.id) AS "Groups",
-  p.name AS "product",
-  c.name AS "category",
+  p.name AS product,
+  c.name AS category,
   g.name AS "group"
 FROM products p
 JOIN categories c ON p.category_id = c.id
 JOIN groups g ON c.group_id = g.id
-JOIN sqlpage_options op ON op.type = 'product' AND p.name = op.name
-JOIN sqlpage_options oc ON oc.type = 'category' AND c.name = oc.name
-JOIN sqlpage_options og ON og.type = 'group' AND g.name = og.name
 WHERE
-  COALESCE(op.selected, oc.selected, og.selected, TRUE)
+  (p.name IN (SELECT v#>>'{}' FROM json_array_elements($products::json) j(v)) OR $products IS NULL)
 AND
-  op.session = sqlpage.cookie('session')
+  (c.name IN (SELECT v#>>'{}' FROM json_array_elements($categories::json) j(v)) OR $categories IS NULL)
 AND
-  oc.session = sqlpage.cookie('session')
-AND
-  og.session = sqlpage.cookie('session')
-GROUP BY ROLLUP (g.name, c.name, p.name)
-ORDER BY product, category, "group") q
-WHERE q.product IS NOT NULL OR q.group IS NULL
+  (g.name IN (SELECT v#>>'{}' FROM json_array_elements($groups::json) j(v)) OR $groups IS NULL)
+GROUP BY ROLLUP ("group", category, product)
 );
-SELECT * FROM sqlpage_products;
 
-SELECT 
-  '# No Data' AS "Notice"
+SET product_options = (
+SELECT
+  json_agg(json_build_object(
+    'label', value,
+    'value', value,
+    'selected', value IN (SELECT v#>>'{}' FROM json_array_elements($products::json) j(v))
+  ) ORDER BY value)
+FROM (
+  SELECT v#>>'{}'
+  FROM json_array_elements($products::json) j(v)
+  UNION
+  SELECT product
+  FROM sqlpage_products
+) o(value)
+WHERE value IS NOT NULL
+);
+
+SELECT 'dynamic' AS component, sqlpage.run_sql('cookie.sql') AS properties;
+SELECT 'dynamic' AS component, sqlpage.run_sql('theme.sql') AS properties;
+SELECT 'dynamic' AS component, sqlpage.run_sql('nav.sql') AS properties;
+
+SELECT
+  'table' AS component,
+  TRUE AS search,
+  TRUE AS striped_rows,
+  TRUE AS small,
+  'Notice' AS markdown
+;
+
+SELECT
+  COALESCE("product", "Products"||'') "Product",
+  COALESCE("category", "Categories"||'') "Category",
+  COALESCE("group", "Groups"||'') "Group"
+FROM sqlpage_products q
+WHERE q.product IS NOT NULL OR q.group IS NULL
+ORDER BY product, category, "group"
+;
+
+SELECT '# No Data' AS "Notice"
 WHERE NOT EXISTS(SELECT * FROM sqlpage_products LIMIT 1);

+ 4 - 0
sqlpage/theme.sql

@@ -0,0 +1,4 @@
+SELECT 'shell' AS component,
+  'dark' AS theme,
+  $title AS title
+;