diff --git a/index.html b/index.html index 28613ec..4024a67 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,7 @@ - BOHA | Admin + Admin
diff --git a/package-lock.json b/package-lock.json index 29448e7..1a683df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "boha-app-ts", + "name": "app-ts", "version": "1.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "boha-app-ts", + "name": "app-ts", "version": "1.1.4", "license": "ISC", "dependencies": { @@ -28,6 +28,7 @@ "framer-motion": "^12.38.0", "hi-base32": "^0.5.1", "jsonwebtoken": "^9.0.3", + "leaflet": "^1.9.4", "node-cron": "^4.2.1", "nodemailer": "^8.0.2", "otpauth": "^9.5.0", @@ -45,6 +46,7 @@ "@types/bcryptjs": "^2.4.6", "@types/dompurify": "^3.0.5", "@types/jsonwebtoken": "^9.0.10", + "@types/leaflet": "^1.9.21", "@types/mysql": "^2.15.27", "@types/node": "^25.5.0", "@types/node-cron": "^3.0.11", @@ -1504,6 +1506,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", @@ -1515,6 +1524,16 @@ "@types/node": "*" } }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -3699,6 +3718,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/light-my-request": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", diff --git a/package.json b/package.json index 3434908..02aaab8 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "framer-motion": "^12.38.0", "hi-base32": "^0.5.1", "jsonwebtoken": "^9.0.3", + "leaflet": "^1.9.4", "node-cron": "^4.2.1", "nodemailer": "^8.0.2", "otpauth": "^9.5.0", @@ -60,6 +61,7 @@ "@types/bcryptjs": "^2.4.6", "@types/dompurify": "^3.0.5", "@types/jsonwebtoken": "^9.0.10", + "@types/leaflet": "^1.9.21", "@types/mysql": "^2.15.27", "@types/node": "^25.5.0", "@types/node-cron": "^3.0.11", diff --git a/prisma/migrations/20260327_add_dark_logo/migration.sql b/prisma/migrations/20260327_add_dark_logo/migration.sql new file mode 100644 index 0000000..ff13d73 --- /dev/null +++ b/prisma/migrations/20260327_add_dark_logo/migration.sql @@ -0,0 +1 @@ +ALTER TABLE `company_settings` ADD COLUMN `logo_data_dark` MEDIUMBLOB NULL AFTER `logo_data`; diff --git a/prisma/migrations/20260327_add_number_patterns/migration.sql b/prisma/migrations/20260327_add_number_patterns/migration.sql new file mode 100644 index 0000000..95e178b --- /dev/null +++ b/prisma/migrations/20260327_add_number_patterns/migration.sql @@ -0,0 +1,4 @@ +ALTER TABLE `company_settings` + ADD COLUMN `offer_number_pattern` VARCHAR(100) NULL, + ADD COLUMN `order_number_pattern` VARCHAR(100) NULL, + ADD COLUMN `invoice_number_pattern` VARCHAR(100) NULL; diff --git a/prisma/migrations/20260327_add_smtp_settings/migration.sql b/prisma/migrations/20260327_add_smtp_settings/migration.sql new file mode 100644 index 0000000..900f806 --- /dev/null +++ b/prisma/migrations/20260327_add_smtp_settings/migration.sql @@ -0,0 +1,3 @@ +ALTER TABLE `company_settings` + ADD COLUMN `smtp_from` VARCHAR(255) NULL, + ADD COLUMN `smtp_from_name` VARCHAR(255) NULL; diff --git a/prisma/migrations/20260327_add_system_settings/migration.sql b/prisma/migrations/20260327_add_system_settings/migration.sql new file mode 100644 index 0000000..74412f4 --- /dev/null +++ b/prisma/migrations/20260327_add_system_settings/migration.sql @@ -0,0 +1,13 @@ +-- System settings columns on company_settings +ALTER TABLE `company_settings` + ADD COLUMN `break_threshold_hours` DECIMAL(4,2) DEFAULT 6, + ADD COLUMN `break_duration_short` INT DEFAULT 15, + ADD COLUMN `break_duration_long` INT DEFAULT 30, + ADD COLUMN `clock_rounding_minutes` INT DEFAULT 15, + ADD COLUMN `invoice_alert_email` VARCHAR(255) NULL, + ADD COLUMN `leave_notify_email` VARCHAR(255) NULL, + ADD COLUMN `max_login_attempts` INT DEFAULT 5, + ADD COLUMN `lockout_minutes` INT DEFAULT 15, + ADD COLUMN `max_requests_per_minute` INT DEFAULT 300, + ADD COLUMN `available_vat_rates` LONGTEXT NULL, + ADD COLUMN `available_currencies` LONGTEXT NULL; diff --git a/prisma/migrations/20260327_consolidate_settings_permissions/migration.sql b/prisma/migrations/20260327_consolidate_settings_permissions/migration.sql new file mode 100644 index 0000000..9019c22 --- /dev/null +++ b/prisma/migrations/20260327_consolidate_settings_permissions/migration.sql @@ -0,0 +1,18 @@ +-- Create new unified permission +INSERT INTO permissions (name, display_name, description, module) +VALUES ('settings.manage', 'Správa nastavení', 'Správa všech nastavení systému', 'settings') +ON DUPLICATE KEY UPDATE display_name = VALUES(display_name); + +-- Grant to all roles that had any of the old 3 +INSERT IGNORE INTO role_permissions (role_id, permission_id) +SELECT DISTINCT rp.role_id, (SELECT id FROM permissions WHERE name = 'settings.manage') +FROM role_permissions rp +JOIN permissions p ON p.id = rp.permission_id +WHERE p.name IN ('offers.settings', 'settings.roles', 'settings.security'); + +-- Clean up old role_permissions +DELETE FROM role_permissions +WHERE permission_id IN (SELECT id FROM permissions WHERE name IN ('offers.settings', 'settings.roles', 'settings.security')); + +-- Remove old permissions +DELETE FROM permissions WHERE name IN ('offers.settings', 'settings.roles', 'settings.security'); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6d60854..49995ea 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -100,6 +100,7 @@ model company_settings { vat_id String? @db.VarChar(50) custom_fields String? @db.LongText logo_data Bytes? + logo_data_dark Bytes? quotation_prefix String? @db.VarChar(20) default_currency String? @default("CZK") @db.VarChar(10) default_vat_rate Decimal? @default(21.00) @db.Decimal(5, 2) @@ -109,7 +110,23 @@ model company_settings { sync_version Int? @default(0) order_type_code String? @db.VarChar(10) invoice_type_code String? @db.VarChar(10) - require_2fa Boolean @default(false) + require_2fa Boolean @default(false) + break_threshold_hours Decimal? @default(6) @db.Decimal(4, 2) + break_duration_short Int? @default(15) + break_duration_long Int? @default(30) + clock_rounding_minutes Int? @default(15) + invoice_alert_email String? @db.VarChar(255) + leave_notify_email String? @db.VarChar(255) + max_login_attempts Int? @default(5) + lockout_minutes Int? @default(15) + max_requests_per_minute Int? @default(300) + available_vat_rates String? @db.LongText + available_currencies String? @db.LongText + smtp_from String? @db.VarChar(255) + smtp_from_name String? @db.VarChar(255) + offer_number_pattern String? @db.VarChar(100) + order_number_pattern String? @db.VarChar(100) + invoice_number_pattern String? @db.VarChar(100) } model customers { diff --git a/src/admin/AdminApp.tsx b/src/admin/AdminApp.tsx index 5913425..6633640 100644 --- a/src/admin/AdminApp.tsx +++ b/src/admin/AdminApp.tsx @@ -32,7 +32,6 @@ const Offers = lazy(() => import("./pages/Offers")); const OfferDetail = lazy(() => import("./pages/OfferDetail")); const OffersCustomers = lazy(() => import("./pages/OffersCustomers")); const OffersTemplates = lazy(() => import("./pages/OffersTemplates")); -const CompanySettings = lazy(() => import("./pages/CompanySettings")); const Orders = lazy(() => import("./pages/Orders")); const OrderDetail = lazy(() => import("./pages/OrderDetail")); const Projects = lazy(() => import("./pages/Projects")); @@ -91,7 +90,6 @@ export default function AdminApp() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/src/admin/components/Sidebar.tsx b/src/admin/components/Sidebar.tsx index dde6e87..aa9f80e 100644 --- a/src/admin/components/Sidebar.tsx +++ b/src/admin/components/Sidebar.tsx @@ -330,22 +330,6 @@ const menuSections: MenuSection[] = [ ), }, - { - path: "/company/settings", - label: "Firma", - permission: "offers.settings", - icon: ( - - - - - ), - }, ], }, { @@ -372,7 +356,7 @@ const menuSections: MenuSection[] = [ { path: "/settings", label: "Nastavení", - permission: ["settings.roles", "settings.security"], + permission: "settings.manage", icon: ( { + (e.target as HTMLImageElement).src = + theme === "dark" + ? "/images/logo-dark.png" + : "/images/logo-light.png"; + }} />