diff --git a/client/package-lock.json b/client/package-lock.json
index 009db09..4d114bf 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -13,8 +13,10 @@
"@emotion/styled": "^11.14.1",
"firebase": "^12.2.1",
"framer-motion": "^12.23.12",
+ "next-themes": "^0.4.6",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "react-icons": "^5.5.0"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
@@ -5569,6 +5571,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -5959,6 +5971,15 @@
"react": "^19.0.0"
}
},
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
diff --git a/client/package.json b/client/package.json
index 1803fde..00ecc98 100644
--- a/client/package.json
+++ b/client/package.json
@@ -15,8 +15,10 @@
"@emotion/styled": "^11.14.1",
"firebase": "^12.2.1",
"framer-motion": "^12.23.12",
+ "next-themes": "^0.4.6",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "react-icons": "^5.5.0"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
diff --git a/client/src/App.jsx b/client/src/App.jsx
index cf179dc..7999065 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -1,8 +1,12 @@
import React, {useState, useEffect} from 'react';
+import { Box, Button, Flex, Heading, Input, VStack, HStack, Text, IconButton } from '@chakra-ui/react';
+import { HiMoon, HiSun } from 'react-icons/hi';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import Login from './components/Login';
-const endpoint = `http://localhost:3001`
+// Local Development
+ const endpoint = `http://localhost:3001`
+// const endpoint = `https://urejevalnik.dztps.si`
// Jezik (Language) Components
const JezikForm = ({jezik, onSubmit, onCancel}) => {
@@ -15,48 +19,58 @@ const JezikForm = ({jezik, onSubmit, onCancel}) => {
};
return (
-
+
+
+
);
};
const JezikList = ({jeziki, onEdit, onDelete}) => (
-
-
-
- | ID |
- Language |
- Actions |
-
-
-
- {jeziki.map(jezik => (
-
- | {jezik.id} |
- {jezik.jezik} |
-
-
-
- |
-
- ))}
-
-
+
+
+
+
+ | ID |
+ Language |
+ Actions |
+
+
+
+ {jeziki.map(jezik => (
+
+ | {jezik.id} |
+ {jezik.jezik} |
+
+
+
+
+
+ |
+
+ ))}
+
+
+
);
// Podrocje (Field) Components
@@ -69,48 +83,59 @@ const PodrocjeForm = ({podrocje, onSubmit, onCancel}) => {
};
return (
-
+
+
+
);
};
const PodrocjeList = ({podrocja, onEdit, onDelete}) => (
-
-
-
- | ID |
- Field |
- Actions |
-
-
-
- {podrocja.map(podrocje => (
-
- | {podrocje.id} |
- {podrocje.podrocje} |
-
-
-
- |
-
- ))}
-
-
+
+
+
+
+ | ID |
+ Field |
+ Actions |
+
+
+
+ {podrocja.map(podrocje => (
+
+ | {podrocje.id} |
+ {podrocje.podrocje} |
+
+
+
+
+
+ |
+
+ ))}
+
+
+
);
// Vloga (Role) Components
@@ -127,74 +152,83 @@ const VlogaForm = ({vloga, onSubmit, onCancel}) => {
};
return (
-
+
+
+
);
};
const VlogaList = ({vloge, onEdit, onDelete}) => (
-
-
-
- | ID |
- Role |
- Female Form |
- Male Form |
- Actions |
-
-
-
- {vloge.map(vloga => (
-
- | {vloga.id} |
- {vloga.vloga} |
- {vloga.izpis_zenska} |
- {vloga.izpis_moski} |
-
-
-
- |
-
- ))}
-
-
+
+
+
+
+ | ID |
+ Role |
+ Female Form |
+ Male Form |
+ Actions |
+
+
+
+ {vloge.map(vloga => (
+
+ | {vloga.id} |
+ {vloga.vloga} |
+ {vloga.izpis_zenska} |
+ {vloga.izpis_moski} |
+
+
+
+
+
+ |
+
+ ))}
+
+
+
);
// Protected App Component (requires authentication)
@@ -206,6 +240,9 @@ const ProtectedApp = () => {
const [members, setMembers] = useState([]);
const [editingMember, setEditingMember] = useState(null);
const [showMemberForm, setShowMemberForm] = useState(false);
+ const [viewingMember, setViewingMember] = useState(null);
+ const [showMemberDetailsModal, setShowMemberDetailsModal] = useState(false);
+ const [memberTranslations, setMemberTranslations] = useState([]);
// Jezik state
const [jeziki, setJeziki] = useState([]);
@@ -393,148 +430,222 @@ const ProtectedApp = () => {
}
};
+ const handleViewMemberDetails = async (member) => {
+ setViewingMember(member);
+ try {
+ const response = await fetch(`${endpoint}/express/api/v1/clan/${member.id}/prevod/`, {
+ headers: {
+ 'Authorization': `Bearer ${authToken}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+ const data = await response.json();
+ setMemberTranslations(data);
+ } catch (error) {
+ console.error('Error fetching member translations:', error);
+ setMemberTranslations([]);
+ }
+ setShowMemberDetailsModal(true);
+ };
+
+ const handleCloseMemberDetailsModal = () => {
+ setShowMemberDetailsModal(false);
+ setViewingMember(null);
+ setMemberTranslations([]);
+ };
+
return (
-
-
- DZTPS Management
-
- Welcome, {currentUser?.email}
-
-
+
+
+
-
-
- setActiveTab('members')}>Members
- setActiveTab('jezik')}>Languages
- setActiveTab('podrocje')}>Fields
- setActiveTab('vloga')}>Roles
-
+
+
+
+ setActiveTab('members')}
+ >
+ Members
+
+ setActiveTab('jezik')}
+ >
+ Languages
+
+ setActiveTab('podrocje')}
+ >
+ Fields
+
+ setActiveTab('vloga')}
+ >
+ Roles
+
+
- {activeTab === 'members' && (
-
-
Members Management
- setShowMemberForm(true)}>Add New Member
- {showMemberForm ? (
- handleSubmit('members', formData)}
- onCancel={() => {
- setEditingMember(null);
- setShowMemberForm(false);
- }}
- />
- ) : (
- {
- setEditingMember(member);
- setShowMemberForm(true);
- }}
- onDelete={(id) => handleDelete('members', id)}
- />
+ {activeTab === 'members' && (
+
+
+ Members Management
+ setShowMemberForm(true)}>
+ Add New Member
+
+
+ {showMemberForm ? (
+ handleSubmit('members', formData)}
+ onCancel={() => {
+ setEditingMember(null);
+ setShowMemberForm(false);
+ }}
+ />
+ ) : (
+ {
+ setEditingMember(member);
+ setShowMemberForm(true);
+ }}
+ onDelete={(id) => handleDelete('members', id)}
+ onViewDetails={handleViewMemberDetails}
+ />
+ )}
+
)}
-
- )}
- {activeTab === 'jezik' && (
-
-
Languages Management
- setShowJezikForm(true)}>Add New Language
- {showJezikForm ? (
- handleSubmit('jezik', formData)}
- onCancel={() => {
- setEditingJezik(null);
- setShowJezikForm(false);
- }}
- />
- ) : (
- {
- setEditingJezik(jezik);
- setShowJezikForm(true);
- }}
- onDelete={(id) => handleDelete('jezik', id)}
- />
+ {activeTab === 'jezik' && (
+
+
+ Languages Management
+ setShowJezikForm(true)}>
+ Add New Language
+
+
+ {showJezikForm ? (
+ handleSubmit('jezik', formData)}
+ onCancel={() => {
+ setEditingJezik(null);
+ setShowJezikForm(false);
+ }}
+ />
+ ) : (
+ {
+ setEditingJezik(jezik);
+ setShowJezikForm(true);
+ }}
+ onDelete={(id) => handleDelete('jezik', id)}
+ />
+ )}
+
)}
-
- )}
- {activeTab === 'podrocje' && (
-
-
Fields Management
-
setShowPodrocjeForm(true)}>Add New Field
- {showPodrocjeForm ? (
-
handleSubmit('podrocje', formData)}
- onCancel={() => {
- setEditingPodrocje(null);
- setShowPodrocjeForm(false);
- }}
- />
- ) : (
- {
- setEditingPodrocje(podrocje);
- setShowPodrocjeForm(true);
- }}
- onDelete={(id) => handleDelete('podrocje', id)}
- />
+ {activeTab === 'podrocje' && (
+
+
+ Fields Management
+ setShowPodrocjeForm(true)}>
+ Add New Field
+
+
+ {showPodrocjeForm ? (
+ handleSubmit('podrocje', formData)}
+ onCancel={() => {
+ setEditingPodrocje(null);
+ setShowPodrocjeForm(false);
+ }}
+ />
+ ) : (
+ {
+ setEditingPodrocje(podrocje);
+ setShowPodrocjeForm(true);
+ }}
+ onDelete={(id) => handleDelete('podrocje', id)}
+ />
+ )}
+
)}
-
- )}
- {activeTab === 'vloga' && (
-
-
Roles Management
- setShowVlogaForm(true)}>Add New Role
- {showVlogaForm ? (
- handleSubmit('vloga', formData)}
- onCancel={() => {
- setEditingVloga(null);
- setShowVlogaForm(false);
- }}
- />
- ) : (
- {
- setEditingVloga(vloga);
- setShowVlogaForm(true);
- }}
- onDelete={(id) => handleDelete('vloga', id)}
- />
+ {activeTab === 'vloga' && (
+
+
+ Roles Management
+ setShowVlogaForm(true)}>
+ Add New Role
+
+
+ {showVlogaForm ? (
+ handleSubmit('vloga', formData)}
+ onCancel={() => {
+ setEditingVloga(null);
+ setShowVlogaForm(false);
+ }}
+ />
+ ) : (
+ {
+ setEditingVloga(vloga);
+ setShowVlogaForm(true);
+ }}
+ onDelete={(id) => handleDelete('vloga', id)}
+ />
+ )}
+
)}
-
- )}
-
-
+
+
+
+
+
);
};
@@ -554,131 +665,456 @@ const TranslationForm = ({translation, languages, fields, roles, onSubmit, onCan
};
return (
-
+ Licenca DZTPS
+
+
+
+
+
+ Fields:
+
+ {fields.map(field => (
+
+ p.id === field.id)}
+ onChange={(e) => {
+ const newPodrocja = e.target.checked
+ ? [...formData.podrocja, field]
+ : formData.podrocja.filter(p => p.id !== field.id);
+ setFormData({...formData, podrocja: newPodrocja});
+ }}
+ style={{
+ width: '16px',
+ height: '16px',
+ accentColor: '#3182ce'
+ }}
+ />
+ {field.podrocje}
+
+ ))}
+
+
+
+
+ Roles:
+
+ {roles.map(role => (
+
+ v.id === role.id)}
+ onChange={(e) => {
+ const newVloge = e.target.checked
+ ? [...formData.vloge, role]
+ : formData.vloge.filter(v => v.id !== role.id);
+ setFormData({...formData, vloge: newVloge});
+ }}
+ style={{
+ width: '16px',
+ height: '16px',
+ accentColor: '#3182ce'
+ }}
+ />
+ {role.vloga}
+
+ ))}
+
+
+
+
+
+
+ {translation ? 'Update' : 'Add'} Translation
+
+
+ Cancel
+
+
+
+
+
+ );
+};
+
+const MemberDetailsDrawer = ({member, translations, isOpen, onClose}) => {
+ const formatDate = (dateString) => {
+ if (!dateString) return '-';
+ return new Date(dateString).toLocaleDateString();
+ };
+
+ const formatPhone = (phone) => {
+ return phone || '-';
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+ e.stopPropagation()}
+ overflowY="auto"
+ >
+
+ {member?.ime} {member?.priimek}
+
+ ×
+
+
+
+
+
+
+
+ Basic Information
+
+
+
+ ID:
+ {member?.id_izkaznica || '-'}
+
+
+ Gender:
+ {member?.spol === 'moski' ? 'Male' : member?.spol === 'zenska' ? 'Female' : '-'}
+
+
+ Birth Date:
+ {formatDate(member?.datum_rojstva)}
+
+
+ Birth Place:
+ {member?.kraj_rojstva || '-'}
+
+
+ Nationality:
+ {member?.drzavljanstvo || '-'}
+
+
+ Native Language:
+ {member?.materni_jezik || '-'}
+
+
+
+
+
+
+ Contact Information
+
+
+
+ Email:
+ {member?.email || '-'}
+
+
+ Home Phone:
+ {formatPhone(member?.telefondoma)}
+
+
+ Work Phone:
+ {formatPhone(member?.telefonsluzba)}
+
+
+ Mobile Phone:
+ {formatPhone(member?.telefonmobi)}
+
+
+ Website:
+ {member?.spletnastran || '-'}
+
+
+
+
+
+
+
+
+ Address
+
+
+
+ Address:
+ {member?.naslovbivalisca || '-'}
+
+
+ Street:
+ {member?.ulica || '-'}
+
+
+ Postal Code:
+ {member?.postna_stevilka || '-'}
+
+
+ Post Office:
+ {member?.posta || '-'}
+
+
+
+
+
+
+ Professional Information
+
+
+
+ Education:
+ {member?.solska_izobrazba || '-'}
+
+
+ Basic Profession:
+ {member?.osnovni_poklic || '-'}
+
+
+ Current Employment:
+ {member?.sedanja_zaposlitev || '-'}
+
+
+ Translation Experience:
+ {member?.prevajalska_praksa || '-'}
+
+
+ Join Date:
+ {formatDate(member?.datum_vclanitve)}
+
+
+ Directory Listed:
+
+ {member?.objava_v_iskalniku ? 'Yes' : 'No'}
+
+
+
+
+
+
+ {member?.nacin_pridobivanja_znanja_tujih_jezikov && (
+
+
+ Language Knowledge Acquisition
+
+ {member.nacin_pridobivanja_znanja_tujih_jezikov}
+
+ )}
+
+ {translations && translations.length > 0 && (
+
+
+ Translation Skills ({translations.length})
+
+
+
+ {translations.map(translation => (
+
+
+
+ {translation.iz} → {translation.v}
+
+
+ {translation.licenca_DZTPS ? 'Licensed' : 'Not Licensed'}
+
+
+ {translation.rang && (
+ Rank: {translation.rang}
+ )}
+
+ {translation.podrocja && translation.podrocja.length > 0 && (
+
+ Fields:
+
+ {translation.podrocja.map(field => (
+
+ {field.podrocje}
+
+ ))}
+
+
+ )}
+ {translation.vloge && translation.vloge.length > 0 && (
+
+ Roles:
+
+ {translation.vloge.map(role => (
+
+ {role.vloga}
+
+ ))}
+
+
+ )}
+
+
+ ))}
+
+
+
+ )}
+
+
+
+
);
};
const TranslationsList = ({translations, onEdit, onDelete}) => (
-
-
-
- | From |
- To |
- Rank |
- License |
- Actions |
-
-
-
- {translations.map(translation => (
-
- | {translation.iz} |
- {translation.v} |
- {translation.rang} |
- {translation.licenca_DZTPS ? 'Yes' : 'No'} |
-
- onEdit(translation)}>Edit
- onDelete(translation.id)}>Delete
- |
-
- ))}
-
-
+
+
+
+
+ | From |
+ To |
+ Rank |
+ License |
+ Fields |
+ Roles |
+ Actions |
+
+
+
+ {translations.map(translation => (
+
+ | {translation.iz} |
+ {translation.v} |
+ {translation.rang || '-'} |
+
+
+ {translation.licenca_DZTPS ? 'Yes' : 'No'}
+
+ |
+
+ {translation.podrocja && translation.podrocja.length > 0 ? (
+
+ {translation.podrocja.map(field => (
+
+ {field.podrocje}
+
+ ))}
+
+ ) : (
+ -
+ )}
+ |
+
+ {translation.vloge && translation.vloge.length > 0 ? (
+
+ {translation.vloge.map(role => (
+
+ {role.vloga}
+
+ ))}
+
+ ) : (
+ -
+ )}
+ |
+
+
+ onEdit(translation)}>Edit
+ onDelete(translation.id)}>Delete
+
+ |
+
+ ))}
+
+
+
);
const MemberForm = ({member, onSubmit, onCancel}) => {
@@ -845,286 +1281,303 @@ const MemberForm = ({member, onSubmit, onCancel}) => {
};
return (
-
+
+
+ )}
+
)
}
-const MembersList = ({members, onEdit, onDelete}) => {
+const MembersList = ({members, onEdit, onDelete, onViewDetails}) => {
return (
-
-
Members List
-
+
+
-
- | ID |
- Name |
- Surname |
- Email |
- Actions |
-
+
+ | ID |
+ Name |
+ Surname |
+ Email |
+ Actions |
+
- {members.map(member => (
-
- | {member.id_izkaznica} |
- {member.ime} |
- {member.priimek} |
- {member.email} |
-
- onEdit(member)}>Edit
- onDelete(member.id)}>Delete
- |
-
- ))}
+ {members.map(member => (
+
+ | {member.id_izkaznica} |
+ {member.ime} |
+ {member.priimek} |
+ {member.email} |
+
+
+ onViewDetails(member)}>View Details
+ onEdit(member)}>Edit
+ onDelete(member.id)}>Delete
+
+ |
+
+ ))}
-
+
);
};
diff --git a/client/src/components/Login.jsx b/client/src/components/Login.jsx
index 630eb6c..076b521 100644
--- a/client/src/components/Login.jsx
+++ b/client/src/components/Login.jsx
@@ -1,4 +1,14 @@
import React, { useState } from 'react';
+import {
+ Box,
+ Button,
+ VStack,
+ HStack,
+ Input,
+ Text,
+ Heading,
+ Flex
+} from '@chakra-ui/react';
import { signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth';
import { auth, googleProvider } from '../firebaseConfig';
@@ -36,100 +46,107 @@ const Login = () => {
};
return (
-
-
Login to DZTPS
-
- {error && (
-
- {error}
-
- )}
-
-
-
-
- OR
-
-
-
+
- {loading ? 'Signing in...' : 'Sign in with Google'}
-
-
+
+
+ Login to DZTPS
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+
+ Email
+ setEmail(e.target.value)}
+ disabled={loading}
+ placeholder="Enter your email"
+ bg="white"
+ required
+ />
+
+
+
+ Password
+ setPassword(e.target.value)}
+ disabled={loading}
+ placeholder="Enter your password"
+ bg="white"
+ required
+ />
+
+
+
+ {loading ? 'Signing in...' : 'Sign in with Email'}
+
+
+
+
+
+
+
+ OR
+
+
+
+
+
+ {loading ? 'Signing in...' : 'Sign in with Google'}
+
+
+
+
);
};
diff --git a/client/src/components/ui/color-mode.jsx b/client/src/components/ui/color-mode.jsx
new file mode 100644
index 0000000..b2d1cd6
--- /dev/null
+++ b/client/src/components/ui/color-mode.jsx
@@ -0,0 +1,90 @@
+'use client'
+
+import { ClientOnly, IconButton, Skeleton, Span } from '@chakra-ui/react'
+import { ThemeProvider, useTheme } from 'next-themes'
+
+import * as React from 'react'
+import { LuMoon, LuSun } from 'react-icons/lu'
+
+export function ColorModeProvider(props) {
+ return (
+
+ )
+}
+
+export function useColorMode() {
+ const { resolvedTheme, setTheme, forcedTheme } = useTheme()
+ const colorMode = forcedTheme || resolvedTheme
+ const toggleColorMode = () => {
+ setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')
+ }
+ return {
+ colorMode: colorMode,
+ setColorMode: setTheme,
+ toggleColorMode,
+ }
+}
+
+export function useColorModeValue(light, dark) {
+ const { colorMode } = useColorMode()
+ return colorMode === 'dark' ? dark : light
+}
+
+export function ColorModeIcon() {
+ const { colorMode } = useColorMode()
+ return colorMode === 'dark' ? :
+}
+
+export const ColorModeButton = React.forwardRef(
+ function ColorModeButton(props, ref) {
+ const { toggleColorMode } = useColorMode()
+ return (
+ }>
+
+
+
+
+ )
+ },
+)
+
+export const LightMode = React.forwardRef(function LightMode(props, ref) {
+ return (
+
+ )
+})
+
+export const DarkMode = React.forwardRef(function DarkMode(props, ref) {
+ return (
+
+ )
+})
diff --git a/client/src/components/ui/provider.jsx b/client/src/components/ui/provider.jsx
new file mode 100644
index 0000000..80e3e01
--- /dev/null
+++ b/client/src/components/ui/provider.jsx
@@ -0,0 +1,12 @@
+'use client'
+
+import { ChakraProvider, defaultSystem } from '@chakra-ui/react'
+import { ColorModeProvider } from './color-mode'
+
+export function Provider(props) {
+ return (
+
+
+
+ )
+}
diff --git a/client/src/components/ui/toaster.jsx b/client/src/components/ui/toaster.jsx
new file mode 100644
index 0000000..6af2b70
--- /dev/null
+++ b/client/src/components/ui/toaster.jsx
@@ -0,0 +1,43 @@
+'use client'
+
+import {
+ Toaster as ChakraToaster,
+ Portal,
+ Spinner,
+ Stack,
+ Toast,
+ createToaster,
+} from '@chakra-ui/react'
+
+export const toaster = createToaster({
+ placement: 'bottom-end',
+ pauseOnPageIdle: true,
+})
+
+export const Toaster = () => {
+ return (
+
+
+ {(toast) => (
+
+ {toast.type === 'loading' ? (
+
+ ) : (
+
+ )}
+
+ {toast.title && {toast.title}}
+ {toast.description && (
+ {toast.description}
+ )}
+
+ {toast.action && (
+ {toast.action.label}
+ )}
+ {toast.closable && }
+
+ )}
+
+
+ )
+}
diff --git a/client/src/components/ui/tooltip.jsx b/client/src/components/ui/tooltip.jsx
new file mode 100644
index 0000000..ab225cc
--- /dev/null
+++ b/client/src/components/ui/tooltip.jsx
@@ -0,0 +1,35 @@
+import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react'
+import * as React from 'react'
+
+export const Tooltip = React.forwardRef(function Tooltip(props, ref) {
+ const {
+ showArrow,
+ children,
+ disabled,
+ portalled = true,
+ content,
+ contentProps,
+ portalRef,
+ ...rest
+ } = props
+
+ if (disabled) return children
+
+ return (
+
+ {children}
+
+
+
+ {showArrow && (
+
+
+
+ )}
+ {content}
+
+
+
+
+ )
+})
diff --git a/client/src/index.css b/client/src/index.css
index 6119ad9..62310ea 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -3,9 +3,9 @@
line-height: 1.5;
font-weight: 400;
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
+ color-scheme: light;
+ color: #213547;
+ background-color: #ffffff;
font-synthesis: none;
text-rendering: optimizeLegibility;
@@ -24,8 +24,6 @@ a:hover {
body {
margin: 0;
- display: flex;
- place-items: center;
min-width: 320px;
min-height: 100vh;
}
@@ -42,7 +40,8 @@ button {
font-size: 1em;
font-weight: 500;
font-family: inherit;
- background-color: #1a1a1a;
+ background-color: #f9f9f9;
+ color: #213547;
cursor: pointer;
transition: border-color 0.25s;
}
@@ -54,15 +53,3 @@ button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
diff --git a/client/src/main.jsx b/client/src/main.jsx
index b9a1a6d..a8fad7e 100644
--- a/client/src/main.jsx
+++ b/client/src/main.jsx
@@ -1,10 +1,15 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
+import { ChakraProvider, createSystem, defaultConfig } from "@chakra-ui/react"
import './index.css'
import App from './App.jsx'
+const system = createSystem(defaultConfig)
+
createRoot(document.getElementById('root')).render(
-
+
+
+
,
)
diff --git a/client/src/tsconfig.app.json b/client/src/tsconfig.app.json
new file mode 100644
index 0000000..c205775
--- /dev/null
+++ b/client/src/tsconfig.app.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "skipLibCheck": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/server/index.js b/server/server/index.js
index a562b89..e470c92 100644
--- a/server/server/index.js
+++ b/server/server/index.js
@@ -4,14 +4,13 @@ const cors = require('cors');
const admin = require('firebase-admin');
const mysql = require('mysql');
const dotenv = require('dotenv');
-const assert = require('assert');
-// const db = require('./config');
+const util = require('util');
// Load environment variables from .env file
dotenv.config();
// Firebase Admin SDK configuration
-const serviceAccount = require('./config/serviceAccountKey.json'); // Update with your serviceAccountKey.json file
+const serviceAccount = require('./config/serviceAccountKey.json'); // Update path if needed
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
@@ -22,16 +21,16 @@ const db = mysql.createConnection({
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
- port: process.env.MYSQL_PORT,
});
-
+
+// Alternative local config (commented)
// const db = mysql.createConnection({
-// host: "localhost",
-// user: "root",
-// password: "",
-// database:"iskalnik_dztps"
+// host: "localhost",
+// user: "root",
+// password: "",
+// database:"iskalnik_dztps"
// });
-
+
db.connect((err) => {
if (err) {
console.error('MySQL connection failed:', err);
@@ -40,19 +39,11 @@ db.connect((err) => {
}
});
-// Helper function to promisify database queries
-const query = (sql, params) => {
- return new Promise((resolve, reject) => {
- db.query(sql, params, (err, result) => {
- if (err) {
- reject(err);
- } else {
- resolve(result);
- }
- });
- });
-};
-
+// --- Promise helpers for mysql (node-mysql, not mysql2) ---
+const query = util.promisify(db.query).bind(db);
+const beginTransaction = util.promisify(db.beginTransaction).bind(db);
+const commit = util.promisify(db.commit).bind(db);
+const rollback = util.promisify(db.rollback).bind(db);
// Express application
const app = express();
@@ -81,8 +72,6 @@ const authenticateFirebaseToken = async (req, res, next) => {
// Protected route example
app.get('/express', authenticateFirebaseToken, (req, res) => {
- // Access user ID from req.uid
- // Fetch data from MySQL using the user ID or perform other operations
res.json({ message: 'Protected route accessed successfully' });
});
@@ -97,114 +86,115 @@ app.listen(PORT, () => {
* clan
*********************************/
-function formClanStruct(from, id=null) {
+function formClanStruct(from, id = null) {
if (!id) id = from.id;
return {
- 'id': id,
- 'id_izkaznica': from.id_izkaznica || "",
- 'ime': from.ime || "",
- 'priimek': from.priimek || "",
- 'spol': from.spol || "",
- 'datum_rojstva': from.datum_rojstva || "",
- 'kraj_rojstva': from.kraj_rojstva || "",
- 'drzavljanstvo': from.drzavljanstvo || "",
- 'solska_izobrazba': from.solska_izobrazba || "",
- 'telefondoma': from.telefondoma || "",
- 'telefonsluzba': from.telefonsluzba || "",
- 'telefonmobi': from.telefonmobi || "",
- 'email': from.email || "",
- 'spletnastran': from.spletnastran || "",
- 'naslovbivalisca': from.naslovbivalisca || "",
- 'ulica': from.ulica || "",
- 'postna_stevilka': from.postna_stevilka || "",
- 'posta': from.posta || "",
- 'osnovni_poklic': from.osnovni_poklic || "",
- 'sedanja_zaposlitev': from.sedanja_zaposlitev || "",
- 'prevajalska_praksa': from.prevajalska_praksa || "",
- 'nacin_pridobivanja_znanja_tujih_jezikov': from.nacin_pridobivanja_znanja_tujih_jezikov || "",
- 'datum_vclanitve': from.datum_vclanitve || "",
- 'materni_jezik': from.materni_jezik || "",
- 'objava_v_iskalniku': from.objava_v_iskalniku || 0
+ id,
+ id_izkaznica: from.id_izkaznica || "",
+ ime: from.ime || "",
+ priimek: from.priimek || "",
+ spol: from.spol || "",
+ datum_rojstva: from.datum_rojstva || "",
+ kraj_rojstva: from.kraj_rojstva || "",
+ drzavljanstvo: from.drzavljanstvo || "",
+ solska_izobrazba: from.solska_izobrazba || "",
+ telefondoma: from.telefondoma || "",
+ telefonsluzba: from.telefonsluzba || "",
+ telefonmobi: from.telefonmobi || "",
+ email: from.email || "",
+ spletnastran: from.spletnastran || "",
+ naslovbivalisca: from.naslovbivalisca || "",
+ ulica: from.ulica || "",
+ postna_stevilka: from.postna_stevilka || "",
+ posta: from.posta || "",
+ osnovni_poklic: from.osnovni_poklic || "",
+ sedanja_zaposlitev: from.sedanja_zaposlitev || "",
+ prevajalska_praksa: from.prevajalska_praksa || "",
+ nacin_pridobivanja_znanja_tujih_jezikov: from.nacin_pridobivanja_znanja_tujih_jezikov || "",
+ datum_vclanitve: from.datum_vclanitve || "",
+ materni_jezik: from.materni_jezik || "",
+ objava_v_iskalniku: from.objava_v_iskalniku || 0
}
}
-function formClanValueArray(res) {
- const body = res.body;
- return [body.id_izkaznica, body.ime, body.priimek, body.spol, body.datum_rojstva ? new Date(body.datum_rojstva) : null,
+function formClanValueArray(req) {
+ const body = req.body || {};
+ return [
+ body.id_izkaznica, body.ime, body.priimek, body.spol,
+ body.datum_rojstva ? new Date(body.datum_rojstva) : null,
body.kraj_rojstva, body.drzavljanstvo, body.solska_izobrazba, body.telefondoma,
body.telefonsluzba, body.telefonmobi, body.email, body.spletnastran, body.naslovbivalisca,
- body.ulica, body.postna_stevilka, body.posta, body.osnovni_poklic, body.sedanja_zaposlitev, body.prevajalska_praksa, body.nacin_pridobivanja_znanja_tujih_jezikov, body.datum_vclanitve ? new Date(body.datum_vclanitve) : null,
+ body.ulica, body.postna_stevilka, body.posta, body.osnovni_poklic, body.sedanja_zaposlitev,
+ body.prevajalska_praksa, body.nacin_pridobivanja_znanja_tujih_jezikov,
+ body.datum_vclanitve ? new Date(body.datum_vclanitve) : null,
body.materni_jezik, body.objava_v_iskalniku
];
}
app.get("/express/api/v1/clan/", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT *
- FROM clan`, (err, result, fields) => {
- if (err) {
- res.sendStatus(500);
- return;
- }
+ db.query(`SELECT * FROM clan`, (err, result) => {
+ if (err) return res.sendStatus(500);
const out = result.map(row => formClanStruct(row));
res.status(200).json(out);
});
});
app.get("/express/api/v1/clan/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT *
- FROM clan
- WHERE id = ?`, [req.params.id], (err, result, fields) => {
- if (result.length === 0 || err) {
- res.sendStatus(404);
- return;
- }
- const first = result[0];
- const out = formClanStruct(first);
- res.status(200).json(out);
+ db.query(`SELECT * FROM clan WHERE id = ?`, [req.params.id], (err, result) => {
+ if (err) return res.sendStatus(500);
+ if (!result.length) return res.sendStatus(404);
+ res.status(200).json(formClanStruct(result[0]));
});
});
app.post("/express/api/v1/clan/", authenticateFirebaseToken, (req, res) => {
- db.query(`INSERT INTO clan (
- id_izkaznica, ime, priimek, spol, datum_rojstva, kraj_rojstva,
- drzavljanstvo, solska_izobrazba, telefondoma, telefonsluzba,
- telefonmobi, email, spletnastran, naslovbivalisca, ulica,
- postna_stevilka, posta, osnovni_poklic, sedanja_zaposlitev, prevajalska_praksa, nacin_pridobivanja_znanja_tujih_jezikov, datum_vclanitve,
- materni_jezik, objava_v_iskalniku
- )
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- `, formClanValueArray(req), (err, result) => {
- if (!err && result.affectedRows === 1) {
- res.status(200).json(formClanStruct(req.body, result.insertId));
- } else {
- res.status(200).json(err);
- // res.sendStatus(400);
+ db.query(
+ `INSERT INTO clan (
+ id_izkaznica, ime, priimek, spol, datum_rojstva, kraj_rojstva,
+ drzavljanstvo, solska_izobrazba, telefondoma, telefonsluzba,
+ telefonmobi, email, spletnastran, naslovbivalisca, ulica,
+ postna_stevilka, posta, osnovni_poklic, sedanja_zaposlitev, prevajalska_praksa,
+ nacin_pridobivanja_znanja_tujih_jezikov, datum_vclanitve, materni_jezik, objava_v_iskalniku
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ formClanValueArray(req),
+ (err, result) => {
+ if (!err && result.affectedRows === 1) {
+ res.status(200).json(formClanStruct(req.body, result.insertId));
+ } else {
+ console.error("INSERT /clan error:", err);
+ res.sendStatus(400);
+ }
}
- });
+ );
});
app.put("/express/api/v1/clan/:id", authenticateFirebaseToken, (req, res) => {
const valueArr = formClanValueArray(req);
valueArr.push(req.params.id);
- db.query(`UPDATE clan
- SET id_izkaznica=?, ime=?, priimek=?, spol=?, datum_rojstva=?, kraj_rojstva=?,
- drzavljanstvo=?, solska_izobrazba=?, telefondoma=?, telefonsluzba=?,
- telefonmobi=?, email=?, spletnastran=?, naslovbivalisca=?, ulica=?,
- postna_stevilka=?, posta=?, osnovni_poklic=?, sedanja_zaposlitev=?, prevajalska_praksa=?, nacin_pridobivanja_znanja_tujih_jezikov=?, datum_vclanitve=?,
- materni_jezik=?, objava_v_iskalniku=?
- WHERE id=?
- `, valueArr, (err, result, fields) => {
- if (!err && result.affectedRows === 1) {
- res.status(200).json(formClanStruct(req.body, req.params.id));
- } else {
- res.sendStatus(400);
+ db.query(
+ `UPDATE clan
+ SET id_izkaznica=?, ime=?, priimek=?, spol=?, datum_rojstva=?, kraj_rojstva=?,
+ drzavljanstvo=?, solska_izobrazba=?, telefondoma=?, telefonsluzba=?,
+ telefonmobi=?, email=?, spletnastran=?, naslovbivalisca=?, ulica=?,
+ postna_stevilka=?, posta=?, osnovni_poklic=?, sedanja_zaposlitev=?, prevajalska_praksa=?,
+ nacin_pridobivanja_znanja_tujih_jezikov=?, datum_vclanitve=?,
+ materni_jezik=?, objava_v_iskalniku=?
+ WHERE id=?`,
+ valueArr,
+ (err, result) => {
+ if (!err && result.affectedRows === 1) {
+ res.status(200).json(formClanStruct(req.body, req.params.id));
+ } else {
+ console.error("UPDATE /clan error:", err);
+ res.sendStatus(400);
+ }
}
- });
+ );
});
app.delete("/express/api/v1/clan/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`DELETE FROM clan
- WHERE id = ?`, [req.params.id], (err, result, fields) => {
+ db.query(`DELETE FROM clan WHERE id = ?`, [req.params.id], (err, result) => {
if (!err && result.affectedRows === 1) {
res.sendStatus(200);
} else {
@@ -213,294 +203,418 @@ app.delete("/express/api/v1/clan/:id", authenticateFirebaseToken, (req, res) =>
});
});
+
/**********************************
- * prevod
+ * prevod (translations)
*********************************/
+// Safe helpers that reject on error
function getVloge(prevodID) {
- return new Promise((resolve) => {
- db.query(`SELECT v.id as 'id', vloga, izpis_zenska, izpis_moski
- FROM prevod_vloga as pv
- JOIN vloga as v
- ON pv.vloga_id = v.id
- WHERE pv.prevod_id = ?
- `, [prevodID], (err, result, fields) => {
- if (err) throw err;
- function formVloga(row) {
- return {
- 'id': row.id,
- 'vloga': row.vloga,
- 'izpis_zenska': row.izpis_zenska,
- 'izpis_moski': row.izpis_moski
- }
+ return new Promise((resolve, reject) => {
+ db.query(
+ `SELECT v.id AS id, v.vloga, v.izpis_zenska, v.izpis_moski
+ FROM prevod_vloga pv
+ JOIN vloga v ON pv.vloga_id = v.id
+ WHERE pv.prevod_id = ?`,
+ [prevodID],
+ (err, result) => {
+ if (err) return reject(err);
+ resolve(result.map(row => ({
+ id: row.id,
+ vloga: row.vloga,
+ izpis_zenska: row.izpis_zenska,
+ izpis_moski: row.izpis_moski
+ })));
}
- resolve(result.map(row => formVloga(row)));
- });
+ );
});
-
-
}
+
function getPodrocja(prevodID) {
- return new Promise((resolve) => {
- db.query(`SELECT p.id as 'id', podrocje
- FROM prevod_podrocje as pp
- JOIN podrocje as p
- ON pp.podrocje_id = p.id
- WHERE pp.prevod_id = ?
- `, [prevodID], (err, result, fields) => {
- if (err) throw err;
- function formPodrocje(row) {
- return {
- 'id': row.id,
- 'podrocje': row.podrocje,
- }
+ return new Promise((resolve, reject) => {
+ db.query(
+ `SELECT p.id AS id, p.podrocje
+ FROM prevod_podrocje pp
+ JOIN podrocje p ON pp.podrocje_id = p.id
+ WHERE pp.prevod_id = ?`,
+ [prevodID],
+ (err, result) => {
+ if (err) return reject(err);
+ resolve(result.map(row => ({ id: row.id, podrocje: row.podrocje })));
}
-
- resolve(result.map(row => formPodrocje(row)));
- });
+ );
});
-
}
-async function formPrevodStruct(from, id=null) {
- if (!id) id = from.id;
+async function formPrevodStruct(from, id = null) {
+ const pid = id || from.id;
const out = {
- 'id': id,
- 'id_clan': from.id_clan,
- 'rang': from.rang,
- 'iz': from.iz,
- 'v': from.v,
- 'licenca_DZTPS': from.licenca_DZTPS,
+ id: pid,
+ id_clan: from.id_clan,
+ rang: from.rang,
+ iz: from.iz,
+ v: from.v,
+ licenca_DZTPS: from.licenca_DZTPS
};
- const podrocja = await getPodrocja(id);
- const vloge = await getVloge(id);
- out.podrocja = podrocja;
- out.vloge = vloge;
+ out.podrocja = await getPodrocja(pid);
+ out.vloge = await getVloge(pid);
return out;
}
+
app.get("/express/api/v1/clan/:id/prevod/", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT p.id as 'id', id_clan, rang, iz.jezik as 'iz', v.jezik as 'v', licenca_DZTPS
- FROM prevod as p
- JOIN jezik as iz
- ON p.iz_id = iz.id
- JOIN jezik as v
- ON p.v_id = v.id
- WHERE id_clan = ?
- `, [req.params.id], async (err, result, fields) => {
- if (err) {
- res.sendStatus(500);
- return;
+ db.query(
+ `SELECT p.id AS id, p.id_clan, p.rang, iz.jezik AS iz, v.jezik AS v, p.licenca_DZTPS
+ FROM prevod p
+ JOIN jezik iz ON p.iz_id = iz.id
+ JOIN jezik v ON p.v_id = v.id
+ WHERE p.id_clan = ?`,
+ [req.params.id],
+ async (err, result) => {
+ if (err) {
+ console.error("DB error (prevod list):", err);
+ return res.sendStatus(500);
+ }
+ try {
+ const out = await Promise.all(result.map(row => formPrevodStruct(row)));
+ res.status(200).json(out);
+ } catch (e) {
+ console.error("Error assembling prevod response:", e);
+ res.sendStatus(500);
+ }
}
- const out = await Promise.all(result.map(row => formPrevodStruct(row)));
- res.status(200).json(out);
- });
+ );
});
app.get("/express/api/v1/clan/:id/prevod/:pid", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT p.id as 'id', id_clan, rang, iz.jezik as 'iz', v.jezik as 'v', licenca_DZTPS
- FROM prevod as p
- JOIN jezik as iz
- ON p.iz_id = iz.id
- JOIN jezik as v
- ON p.v_id = v.id
- WHERE p.id_clan = ? AND p.id = ?
- `, [req.params.id, req.params.pid], async (err, result, fields) => {
- if (err || result.length === 0) {
- res.sendStatus(404);
- return;
+ db.query(
+ `SELECT p.id AS id, p.id_clan, p.rang, iz.jezik AS iz, v.jezik AS v, p.licenca_DZTPS
+ FROM prevod p
+ JOIN jezik iz ON p.iz_id = iz.id
+ JOIN jezik v ON p.v_id = v.id
+ WHERE p.id_clan = ? AND p.id = ?`,
+ [req.params.id, req.params.pid],
+ async (err, result) => {
+ if (err) return res.sendStatus(500);
+ if (!result.length) return res.sendStatus(404);
+ try {
+ const out = await formPrevodStruct(result[0]);
+ res.status(200).json(out);
+ } catch (e) {
+ console.error("Error assembling single prevod:", e);
+ res.sendStatus(500);
+ }
}
- const first = result[0];
- const out = await formPrevodStruct(first);
- res.status(200).json(out);
- });
+ );
});
+// Helpers to add/remove relations
async function addPrevodVloga(prevodID, vloga) {
- await query(`INSERT INTO prevod_vloga
-(prevod_id, vloga_id)
-VALUES
-(?, (
- SELECT v.id
- FROM vloga as v
- WHERE v.vloga = ?
-))
-`, [prevodID, vloga]);
+ await query(
+ `INSERT INTO prevod_vloga (prevod_id, vloga_id)
+ VALUES (?, (SELECT v.id FROM vloga v WHERE v.vloga = ?))`,
+ [prevodID, vloga]
+ );
}
async function removePrevodVloga(prevodID, vloga) {
- await query(`DELETE FROM prevod_vloga
-WHERE prevod_id=? AND vloga_id=(
- SELECT v.id
- FROM vloga as v
- WHERE v.vloga = ?
- )
-`, [prevodID, vloga]);
+ await query(
+ `DELETE FROM prevod_vloga
+ WHERE prevod_id = ? AND vloga_id = (SELECT v.id FROM vloga v WHERE v.vloga = ?)`,
+ [prevodID, vloga]
+ );
}
-
async function addPrevodPodrocje(prevodID, podrocje) {
- await query(`INSERT INTO prevod_podrocje
-(prevod_id, podrocje_id)
-VALUES
-(?, (
- SELECT p.id
- FROM podrocje as p
- WHERE p.podrocje=?
- ))
-`, [prevodID, podrocje]);
+ await query(
+ `INSERT INTO prevod_podrocje (prevod_id, podrocje_id)
+ VALUES (?, (SELECT p.id FROM podrocje p WHERE p.podrocje = ?))`,
+ [prevodID, podrocje]
+ );
}
-
async function removePrevodPodrocje(prevodID, podrocje) {
- await query(`DELETE FROM prevod_podrocje
-WHERE prevod_id=? AND podrocje_id=(
- SELECT p.id
- FROM podrocje as p
- WHERE p.podrocje = ?
- )
-`, [prevodID, podrocje]);
+ await query(
+ `DELETE FROM prevod_podrocje
+ WHERE prevod_id = ? AND podrocje_id = (SELECT p.id FROM podrocje p WHERE p.podrocje = ?)`,
+ [prevodID, podrocje]
+ );
}
-app.post("/express/api/v1/clan/:id/prevod/", async (req, res) => {
- await db.beginTransaction();
+function namesFromArray(arr, key) {
+ if (!Array.isArray(arr)) return [];
+ return arr.map(x => (typeof x === 'string' ? x : (x?.[key] ?? ''))).filter(Boolean);
+}
+
+// Create translation
+app.post("/express/api/v1/clan/:id/prevod/", authenticateFirebaseToken, async (req, res) => {
+ const { id: pathId } = req.params;
+ const { id_clan, rang, iz, v, licenca_DZTPS, podrocja = [], vloge = [] } = req.body || {};
+
+ if (String(pathId) !== String(id_clan)) {
+ return res.status(400).json({ message: "id_clan in body must match :id in path" });
+ }
+ if (!rang || !iz || !v) {
+ return res.status(400).json({ message: "Missing required fields: rang, iz, v" });
+ }
+
try {
- assert(req.body.id_clan == req.params.id);
- const prevod = await query(`INSERT INTO prevod
-(id_clan, rang, iz_id, v_id, licenca_DZTPS)
-VALUES
-(?, ?, (
- SELECT iz.id
- FROM jezik as iz
- WHERE iz.jezik=?
-), (
- SELECT v.id
- FROM jezik as v
- WHERE v.jezik=?
- ), ?)
-`, [req.body.id_clan, req.body.rang, req.body.iz, req.body.v, req.body.licenca_DZTPS]);
+ await beginTransaction();
- const prevodID = prevod.insertId;
- for (let podrocje of req.body.podrocja) {
- await addPrevodPodrocje(prevodID, podrocje.podrocje);
- }
- for (let vloga of req.body.vloge) {
- await addPrevodVloga(prevodID, vloga.vloga);
+ // Resolve language IDs
+ const langs = await query(`SELECT id, jezik FROM jezik WHERE jezik IN (?, ?)`, [iz, v]);
+ const izRow = langs.find(r => r.jezik === iz);
+ const vRow = langs.find(r => r.jezik === v);
+ if (!izRow || !vRow) {
+ await rollback();
+ return res.status(400).json({ message: "Unknown language in iz or v" });
}
- await db.commit();
- res.status(200).json(await formPrevodStruct(req.body, prevodID));
+ const insert = await query(
+ `INSERT INTO prevod (id_clan, rang, iz_id, v_id, licenca_DZTPS)
+ VALUES (?, ?, ?, ?, ?)`,
+ [id_clan, rang, izRow.id, vRow.id, licenca_DZTPS ?? 0]
+ );
+ const prevodID = insert.insertId;
+
+ // Podrocja
+ const podNames = namesFromArray(podrocja, 'podrocje');
+ if (podNames.length) {
+ const rows = await query(
+ `SELECT id, podrocje FROM podrocje WHERE podrocje IN (${podNames.map(() => '?').join(',')})`,
+ podNames
+ );
+ const mapIds = new Map(rows.map(r => [r.podrocje, r.id]));
+ const missing = podNames.filter(n => !mapIds.has(n));
+ if (missing.length) {
+ await rollback();
+ return res.status(400).json({ message: "Unknown podrocje", details: missing });
+ }
+ const values = podNames.map(n => [prevodID, mapIds.get(n)]);
+ await query(
+ `INSERT INTO prevod_podrocje (prevod_id, podrocje_id)
+ VALUES ${values.map(() => '(?, ?)').join(',')}`,
+ values.flat()
+ );
+ }
+
+ // Vloge
+ const vlogaNames = namesFromArray(vloge, 'vloga');
+ if (vlogaNames.length) {
+ const rows = await query(
+ `SELECT id, vloga FROM vloga WHERE vloga IN (${vlogaNames.map(() => '?').join(',')})`,
+ vlogaNames
+ );
+ const mapIds = new Map(rows.map(r => [r.vloga, r.id]));
+ const missing = vlogaNames.filter(n => !mapIds.has(n));
+ if (missing.length) {
+ await rollback();
+ return res.status(400).json({ message: "Unknown vloga", details: missing });
+ }
+ const values = vlogaNames.map(n => [prevodID, mapIds.get(n)]);
+ await query(
+ `INSERT INTO prevod_vloga (prevod_id, vloga_id)
+ VALUES ${values.map(() => '(?, ?)').join(',')}`,
+ values.flat()
+ );
+ }
+
+ await commit();
+
+ const [row] = await query(
+ `SELECT p.id AS id, p.id_clan, p.rang, iz.jezik AS iz, v.jezik AS v, p.licenca_DZTPS
+ FROM prevod p
+ JOIN jezik iz ON p.iz_id = iz.id
+ JOIN jezik v ON p.v_id = v.id
+ WHERE p.id = ?`,
+ [prevodID]
+ );
+ const payload = await formPrevodStruct(row);
+ return res.status(200).json(payload);
+
} catch (e) {
- db.rollback();
- res.sendStatus(400);
+ try { await rollback(); } catch {}
+ console.error("POST /clan/:id/prevod error:", e);
+ return res.status(400).json({ message: "Failed to create prevod", error: e && e.message });
}
});
-app.put("/express/api/v1/clan/:id/prevod/:pid", async (req, res) => {
- await db.beginTransaction();
+// Update translation
+app.put("/express/api/v1/clan/:id/prevod/:pid", authenticateFirebaseToken, async (req, res) => {
+ const { id: pathId, pid } = req.params;
+ const { id_clan, rang, iz, v, licenca_DZTPS, podrocja = [], vloge = [] } = req.body || {};
+
+ if (String(pathId) !== String(id_clan)) {
+ return res.status(400).json({ message: "id_clan in body must match :id in path" });
+ }
+
try {
- await query(`UPDATE prevod
- SET id_clan=?,
- rang=?,
- iz_id=(SELECT iz.id
- FROM jezik as iz
- WHERE iz.jezik = ?),
- v_id=(SELECT v.id
- FROM jezik as v
- WHERE v.jezik = ?),
- licenca_DZTPS=?
- WHERE id_clan=? AND id=?
- `, [req.body.id_clan, req.body.rang, req.body.iz, req.body.v, req.body.licenca_DZTPS, req.params.id, req.params.pid]);
+ await beginTransaction();
- const prevod = await formPrevodStruct(req.body);
+ // Resolve language IDs if provided strings
+ const langsNeeded = [];
+ if (iz) langsNeeded.push(iz);
+ if (v) langsNeeded.push(v);
- // Synchronize podrocja
- const oldPodrocja = prevod.podrocja;
- const newPodrocja = req.body.podrocja;
- for (let podrocje of oldPodrocja) {
- // Podrocje not present in new -> delete
- if (!newPodrocja.some(p => p.id === podrocje.id))
- await removePrevodPodrocje(prevod.id, podrocje.podrocje);
- }
- for (let podrocje of newPodrocja) {
- // Podrocje present in new but not in old -> add
- if (!oldPodrocja.some(p => p.id === podrocje.id))
- await addPrevodPodrocje(prevod.id, podrocje.podrocje);
- }
- // Synchronize vloge
- const oldVloge = prevod.vloge;
- const newVloge = req.body.vloge;
- for (let vloga of oldVloge) {
- // Vloga not present in new -> delete
- if (!newVloge.some(v => v.id === vloga.id))
- await removePrevodVloga(prevod.id, vloga.vloga);
- }
- for (let vloga of newVloge) {
- // Vloga present in new but not in old -> add
- if (!oldVloge.some(v => v.id === vloga.id))
- await addPrevodVloga(prevod.id, vloga.vloga);
+ let izId = null, vId = null;
+ if (langsNeeded.length) {
+ const langRows = await query(
+ `SELECT id, jezik FROM jezik WHERE jezik IN (${langsNeeded.map(() => '?').join(',')})`,
+ langsNeeded
+ );
+ const map = new Map(langRows.map(r => [r.jezik, r.id]));
+ if (iz && !map.has(iz)) { await rollback(); return res.status(400).json({ message: "Unknown language (iz)" }); }
+ if (v && !map.has(v)) { await rollback(); return res.status(400).json({ message: "Unknown language (v)" }); }
+ if (iz) izId = map.get(iz);
+ if (v) vId = map.get(v);
}
- await db.commit();
- res.status(200).json(await formPrevodStruct(req.body));
+ // Build dynamic UPDATE (keep current values if not provided)
+ const sets = [];
+ const params = [];
+ if (id_clan != null) { sets.push(`id_clan = ?`); params.push(id_clan); }
+ if (rang != null) { sets.push(`rang = ?`); params.push(rang); }
+ if (izId != null) { sets.push(`iz_id = ?`); params.push(izId); }
+ if (vId != null) { sets.push(`v_id = ?`); params.push(vId); }
+ if (licenca_DZTPS != null) { sets.push(`licenca_DZTPS = ?`); params.push(licenca_DZTPS); }
+
+ if (sets.length) {
+ params.push(pathId, pid);
+ const sql = `UPDATE prevod SET ${sets.join(', ')} WHERE id_clan = ? AND id = ?`;
+ const upd = await query(sql, params);
+ if (upd.affectedRows === 0) {
+ await rollback();
+ return res.sendStatus(404);
+ }
+ }
+
+ // Fetch old relations BEFORE syncing
+ const oldPodrocja = await getPodrocja(pid);
+ const oldVloge = await getVloge(pid);
+
+ // Sync Podrocja
+ const newPodrocjaNames = namesFromArray(podrocja, 'podrocje');
+ if (newPodrocjaNames.length) {
+ // Map names -> ids
+ const rows = await query(
+ `SELECT id, podrocje FROM podrocje WHERE podrocje IN (${newPodrocjaNames.map(() => '?').join(',')})`,
+ newPodrocjaNames
+ );
+ const mapIds = new Map(rows.map(r => [r.podrocje, r.id]));
+ const missing = newPodrocjaNames.filter(n => !mapIds.has(n));
+ if (missing.length) {
+ await rollback();
+ return res.status(400).json({ message: "Unknown podrocje", details: missing });
+ }
+
+ // Compute deltas by name
+ const oldNames = new Set(oldPodrocja.map(p => p.podrocje));
+ const newNames = new Set(newPodrocjaNames);
+
+ // Deletes
+ for (const name of oldNames) {
+ if (!newNames.has(name)) {
+ await removePrevodPodrocje(pid, name);
+ }
+ }
+ // Adds
+ for (const name of newNames) {
+ if (!oldNames.has(name)) {
+ await addPrevodPodrocje(pid, name);
+ }
+ }
+ }
+
+ // Sync Vloge
+ const newVlogaNames = namesFromArray(vloge, 'vloga');
+ if (newVlogaNames.length) {
+ const rows = await query(
+ `SELECT id, vloga FROM vloga WHERE vloga IN (${newVlogaNames.map(() => '?').join(',')})`,
+ newVlogaNames
+ );
+ const mapIds = new Map(rows.map(r => [r.vloga, r.id]));
+ const missing = newVlogaNames.filter(n => !mapIds.has(n));
+ if (missing.length) {
+ await rollback();
+ return res.status(400).json({ message: "Unknown vloga", details: missing });
+ }
+
+ const oldNames = new Set(oldVloge.map(v => v.vloga));
+ const newNames = new Set(newVlogaNames);
+
+ for (const name of oldNames) {
+ if (!newNames.has(name)) {
+ await removePrevodVloga(pid, name);
+ }
+ }
+ for (const name of newNames) {
+ if (!oldNames.has(name)) {
+ await addPrevodVloga(pid, name);
+ }
+ }
+ }
+
+ await commit();
+
+ // Return the updated record
+ const [row] = await query(
+ `SELECT p.id AS id, p.id_clan, p.rang, iz.jezik AS iz, v.jezik AS v, p.licenca_DZTPS
+ FROM prevod p
+ JOIN jezik iz ON p.iz_id = iz.id
+ JOIN jezik v ON p.v_id = v.id
+ WHERE p.id = ?`,
+ [pid]
+ );
+ if (!row) return res.sendStatus(404);
+ const payload = await formPrevodStruct(row);
+ res.status(200).json(payload);
+
} catch (e) {
- db.rollback();
- res.sendStatus(400);
+ try { await rollback(); } catch {}
+ console.error("PUT /clan/:id/prevod/:pid error:", e);
+ res.status(400).json({ message: "Failed to update prevod", error: e && e.message });
}
});
-app.delete("/express/api/v1/clan/:id/prevod/:pid", async (req, res) => {
- await db.beginTransaction();
+app.delete("/express/api/v1/clan/:id/prevod/:pid", authenticateFirebaseToken, async (req, res) => {
try {
- await query(`DELETE FROM prevod_podrocje WHERE prevod_id=?`, [req.params.pid]);
- await query(`DELETE FROM prevod_vloga WHERE prevod_id=?`, [req.params.pid]);
- await query(`DELETE FROM prevod WHERE id_clan=? AND id=?`, [req.params.id, req.params.pid]);
- await db.commit();
+ await beginTransaction();
+ await query(`DELETE FROM prevod_podrocje WHERE prevod_id = ?`, [req.params.pid]);
+ await query(`DELETE FROM prevod_vloga WHERE prevod_id = ?`, [req.params.pid]);
+ const del = await query(`DELETE FROM prevod WHERE id_clan = ? AND id = ?`, [req.params.id, req.params.pid]);
+ if (!del.affectedRows) { await rollback(); return res.sendStatus(404); }
+ await commit();
res.sendStatus(200);
} catch (e) {
- db.rollback();
+ try { await rollback(); } catch {}
+ console.error("DELETE /clan/:id/prevod/:pid error:", e);
res.sendStatus(400);
}
});
+
/**********************************
* jezik
*********************************/
-function formJezikStruct(from, id=null) {
+function formJezikStruct(from, id = null) {
if (!id) id = from.id;
- return {
- 'id': id,
- 'jezik': from.jezik
- };
+ return { id, jezik: from.jezik };
}
app.get("/express/api/v1/jezik/", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT id, jezik
- FROM jezik`, (err, result, fields) => {
- if (err) {
- res.sendStatus(500);
- return;
- }
- const out = result.map(row => formJezikStruct(row));
- res.status(200).json(out);
+ db.query(`SELECT id, jezik FROM jezik`, (err, result) => {
+ if (err) return res.sendStatus(500);
+ res.status(200).json(result.map(row => formJezikStruct(row)));
});
});
app.get("/express/api/v1/jezik/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT id, jezik
- FROM jezik
- WHERE id = ?`, [req.params.id], (err, result, fields) => {
- if (result.length === 0 || err) {
- res.sendStatus(404);
- return;
- }
- const first = result[0];
- const out = formJezikStruct(first);
- res.status(200).json(out);
+ db.query(`SELECT id, jezik FROM jezik WHERE id = ?`, [req.params.id], (err, result) => {
+ if (err) return res.sendStatus(500);
+ if (!result.length) return res.sendStatus(404);
+ res.status(200).json(formJezikStruct(result[0]));
});
});
app.post("/express/api/v1/jezik/", authenticateFirebaseToken, (req, res) => {
- db.query(`INSERT INTO jezik
- (jezik)
- VALUES (?)`, [req.body.jezik], (err, result) => {
+ db.query(`INSERT INTO jezik (jezik) VALUES (?)`, [req.body.jezik], (err, result) => {
if (!err && result.affectedRows === 1) {
res.status(200).json(formJezikStruct(req.body, result.insertId));
} else {
@@ -510,9 +624,7 @@ app.post("/express/api/v1/jezik/", authenticateFirebaseToken, (req, res) => {
});
app.put("/express/api/v1/jezik/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`UPDATE jezik
- SET jezik = ?
- WHERE id = ?`, [req.body.jezik, req.params.id], (err, result) => {
+ db.query(`UPDATE jezik SET jezik = ? WHERE id = ?`, [req.body.jezik, req.params.id], (err, result) => {
if (!err && result.affectedRows === 1) {
res.status(200).json(formJezikStruct(req.body, req.params.id));
} else {
@@ -522,8 +634,7 @@ app.put("/express/api/v1/jezik/:id", authenticateFirebaseToken, (req, res) => {
});
app.delete("/express/api/v1/jezik/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`DELETE FROM jezik
- WHERE id = ?`, [req.params.id], (err, result, fields) => {
+ db.query(`DELETE FROM jezik WHERE id = ?`, [req.params.id], (err, result) => {
if (!err && result.affectedRows === 1) {
res.sendStatus(200);
} else {
@@ -532,73 +643,66 @@ app.delete("/express/api/v1/jezik/:id", authenticateFirebaseToken, (req, res) =>
});
});
+
/**********************************
* vloga
*********************************/
-function formVlogaStruct(from, id=null) {
+function formVlogaStruct(from, id = null) {
if (!id) id = from.id;
return {
- 'id': id,
- 'vloga': from.vloga,
- 'izpis_zenska': from.izpis_zenska,
- 'izpis_moski': from.izpis_moski
+ id,
+ vloga: from.vloga,
+ izpis_zenska: from.izpis_zenska,
+ izpis_moski: from.izpis_moski
};
}
app.get("/express/api/v1/vloga/", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT id, vloga, izpis_zenska, izpis_moski
- FROM vloga`, (err, result, fields) => {
- if (err) {
- res.sendStatus(500);
- return;
- }
- const out = result.map(row => formVlogaStruct(row));
- res.status(200).json(out);
+ db.query(`SELECT id, vloga, izpis_zenska, izpis_moski FROM vloga`, (err, result) => {
+ if (err) return res.sendStatus(500);
+ res.status(200).json(result.map(row => formVlogaStruct(row)));
});
});
app.get("/express/api/v1/vloga/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT id, vloga, izpis_zenska, izpis_moski
- FROM vloga
- WHERE id = ?`, [req.params.id], (err, result, fields) => {
- if (result.length === 0 || err) {
- res.sendStatus(404);
- return;
- }
- const first = result[0];
- const out = formVlogaStruct(first);
- res.status(200).json(out);
+ db.query(`SELECT id, vloga, izpis_zenska, izpis_moski FROM vloga WHERE id = ?`, [req.params.id], (err, result) => {
+ if (err) return res.sendStatus(500);
+ if (!result.length) return res.sendStatus(404);
+ res.status(200).json(formVlogaStruct(result[0]));
});
});
app.post("/express/api/v1/vloga/", authenticateFirebaseToken, (req, res) => {
- db.query(`INSERT INTO vloga
- (vloga, izpis_zenska, izpis_moski)
- VALUES (?, ?, ?)`, [req.body.vloga, req.body.izpis_zenska, req.body.izpis_moski], (err, result) => {
- if (!err && result.affectedRows === 1) {
- res.status(200).json(formVlogaStruct(req.body, result.insertId));
- } else {
- res.sendStatus(400);
+ db.query(
+ `INSERT INTO vloga (vloga, izpis_zenska, izpis_moski) VALUES (?, ?, ?)`,
+ [req.body.vloga, req.body.izpis_zenska, req.body.izpis_moski],
+ (err, result) => {
+ if (!err && result.affectedRows === 1) {
+ res.status(200).json(formVlogaStruct(req.body, result.insertId));
+ } else {
+ res.sendStatus(400);
+ }
}
- });
+ );
});
app.put("/express/api/v1/vloga/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`UPDATE vloga
- SET vloga = ?, izpis_zenska = ?, izpis_moski = ?
- WHERE id = ?`, [req.body.vloga, req.body.izpis_zenska, req.body.izpis_moski, req.params.id], (err, result) => {
- if (!err && result.affectedRows === 1) {
- res.status(200).json(formVlogaStruct(req.body, req.params.id));
- } else {
- res.sendStatus(400);
+ db.query(
+ `UPDATE vloga SET vloga = ?, izpis_zenska = ?, izpis_moski = ? WHERE id = ?`,
+ [req.body.vloga, req.body.izpis_zenska, req.body.izpis_moski, req.params.id],
+ (err, result) => {
+ if (!err && result.affectedRows === 1) {
+ res.status(200).json(formVlogaStruct(req.body, req.params.id));
+ } else {
+ res.sendStatus(400);
+ }
}
- });
+ );
});
app.delete("/express/api/v1/vloga/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`DELETE FROM vloga
- WHERE id = ?`, [req.params.id], (err, result, fields) => {
+ db.query(`DELETE FROM vloga WHERE id = ?`, [req.params.id], (err, result) => {
if (!err && result.affectedRows === 1) {
res.sendStatus(200);
} else {
@@ -607,48 +711,33 @@ app.delete("/express/api/v1/vloga/:id", authenticateFirebaseToken, (req, res) =>
});
});
+
/**********************************
* podrocje
*********************************/
-function formPodrocjeStruct(from, id=null) {
+function formPodrocjeStruct(from, id = null) {
if (!id) id = from.id;
- return {
- 'id': id,
- 'podrocje': from.podrocje,
- };
+ return { id, podrocje: from.podrocje };
}
app.get("/express/api/v1/podrocje", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT id, podrocje
- FROM podrocje`, (err, result, fields) => {
- if (err) {
- res.sendStatus(500);
- return;
- }
- const out = result.map(row => formPodrocjeStruct(row));
- res.status(200).json(out);
+ db.query(`SELECT id, podrocje FROM podrocje`, (err, result) => {
+ if (err) return res.sendStatus(500);
+ res.status(200).json(result.map(row => formPodrocjeStruct(row)));
});
});
app.get("/express/api/v1/podrocje/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`SELECT id, podrocje
- FROM podrocje
- WHERE id = ?`, [req.params.id], (err, result, fields) => {
- if (result.length === 0 || err) {
- res.sendStatus(404);
- return;
- }
- const first = result[0];
- const out = formPodrocjeStruct(first);
- res.status(200).json(out);
+ db.query(`SELECT id, podrocje FROM podrocje WHERE id = ?`, [req.params.id], (err, result) => {
+ if (err) return res.sendStatus(500);
+ if (!result.length) return res.sendStatus(404);
+ res.status(200).json(formPodrocjeStruct(result[0]));
});
});
app.post("/express/api/v1/podrocje/", authenticateFirebaseToken, (req, res) => {
- db.query(`INSERT INTO podrocje
- (podrocje)
- VALUES (?)`, [req.body.podrocje], (err, result) => {
+ db.query(`INSERT INTO podrocje (podrocje) VALUES (?)`, [req.body.podrocje], (err, result) => {
if (!err && result.affectedRows === 1) {
res.status(200).json(formPodrocjeStruct(req.body, result.insertId));
} else {
@@ -658,37 +747,39 @@ app.post("/express/api/v1/podrocje/", authenticateFirebaseToken, (req, res) => {
});
app.put("/express/api/v1/podrocje/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`UPDATE podrocje
- SET podrocje = ?
- WHERE id = ?`, [req.body.podrocje, req.params.id], (err, result, fields) => {
- if (!err && result.affectedRows === 1) {
- res.status(200).json(formPodrocjeStruct(req.body, req.params.id));
- } else {
- res.sendStatus(400);
+ db.query(
+ `UPDATE podrocje SET podrocje = ? WHERE id = ?`,
+ [req.body.podrocje, req.params.id],
+ (err, result) => {
+ if (!err && result.affectedRows === 1) {
+ res.status(200).json(formPodrocjeStruct(req.body, req.params.id));
+ } else {
+ res.sendStatus(400);
+ }
}
- });
+ );
});
app.delete("/express/api/v1/podrocje/:id", authenticateFirebaseToken, (req, res) => {
- db.query(`DELETE FROM podrocje
- WHERE id = ?`, [req.params.id], (err, result, fields) => {
+ db.query(`DELETE FROM podrocje WHERE id = ?`, [req.params.id], (err, result) => {
if (!err && result.affectedRows === 1) {
res.sendStatus(200);
} else {
res.sendStatus(400);
}
});
-
});
-// ISKALNIK
+/**********************************
+ * ISKALNIK (search)
+ *********************************/
app.get('/express_backend', (req, res) => {
res.send({ express: `YOUR BACKEND IS CONNECTED` });
});
-app.post('/express_backend/api/v1/iskanje', (req,res) => {
+app.post('/express_backend/api/v1/iskanje', (req, res) => {
const iskanjeIme = req.body.iskanjeNiz;
const iskanjeCrka = req.body.iskanjeCrkaPriimek;
const iskanjeIz = req.body.iskanjeIzvirniJezik;
@@ -701,6 +792,7 @@ app.post('/express_backend/api/v1/iskanje', (req,res) => {
// Build query
const params = [];
const queryConditions = [];
+
if (iskanjeIme) {
queryConditions.push("(LOWER(CONCAT(c.ime, ' ', c.priimek)) LIKE LOWER(?))");
params.push(`%${iskanjeIme}%`);
@@ -718,219 +810,217 @@ app.post('/express_backend/api/v1/iskanje', (req,res) => {
params.push(iskanjeV);
}
const generateQuestionMarks = (n) => Array.from({ length: n }, () => '?').join(',');
- if (iskanjeRangi) {
+ if (Array.isArray(iskanjeRangi) && iskanjeRangi.length) {
queryConditions.push(`(p.rang IN (${generateQuestionMarks(iskanjeRangi.length)}))`);
params.push(...iskanjeRangi);
}
-
- if (iskanjeLicenca) {
- queryConditions.push("(p.licenca_DZTPS = ?)")
- params.push(iskanjeLicenca)
+ if (iskanjeLicenca != null) {
+ queryConditions.push("(p.licenca_DZTPS = ?)");
+ params.push(iskanjeLicenca);
}
+ const where = [
+ "c.objava_v_iskalniku = 1",
+ ...(queryConditions.length ? queryConditions : [])
+ ].join(" AND ");
- db.query(`SELECT c.id as 'id' , c.ime as 'ime', c.priimek as 'priimek', c.spol as 'spol', c.telefondoma as 'telefondoma', c.telefonsluzba as 'telefonsluzba',
- c.telefonmobi as 'telefonmobi', c.email as 'email', c.spletnastran as 'spletnastran',
- p.rang as 'rang', iz.jezik as iz, v.jezik as v, p.licenca_DZTPS as 'licenca',
- (
- SELECT GROUP_CONCAT(po.podrocje SEPARATOR ';')
- FROM prevod_podrocje as pp
- JOIN podrocje as po
- ON pp.podrocje_id = po.id
- WHERE pp.prevod_id = p.id
- GROUP BY pp.prevod_id
- ) as 'podrocja',
- (
- SELECT GROUP_CONCAT(vl.vloga SEPARATOR ';')
- FROM prevod_vloga as pv
- JOIN vloga as vl
- ON pv.vloga_id = vl.id
- WHERE pv.prevod_id = p.id
- GROUP BY pv.prevod_id
- ) as 'vloge'
-FROM clan as c
-JOIN prevod as p
-ON c.id = p.id_clan
-JOIN jezik as iz
-ON p.iz_id = iz.id
-JOIN jezik as v
-ON p.v_id = v.id
-WHERE c.objava_v_iskalniku = 1
-WHERE ${queryConditions.length ? queryConditions.join("AND") : "1=1"}`,
- params, (err, rows, fields)=> {
- if (err) throw err;
- if (rows.length == 0) {
- res.send({});
- return;
+ db.query(
+ `SELECT
+ c.id AS id, c.ime AS ime, c.priimek AS priimek, c.spol AS spol,
+ c.telefondoma AS telefondoma, c.telefonsluzba AS telefonsluzba,
+ c.telefonmobi AS telefonmobi, c.email AS email, c.spletnastran AS spletnastran,
+ p.rang AS rang, iz.jezik AS iz, v.jezik AS v, p.licenca_DZTPS AS licenca,
+ (
+ SELECT GROUP_CONCAT(po.podrocje SEPARATOR ';')
+ FROM prevod_podrocje pp
+ JOIN podrocje po ON pp.podrocje_id = po.id
+ WHERE pp.prevod_id = p.id
+ GROUP BY pp.prevod_id
+ ) AS podrocja,
+ (
+ SELECT GROUP_CONCAT(vl.vloga SEPARATOR ';')
+ FROM prevod_vloga pv
+ JOIN vloga vl ON pv.vloga_id = vl.id
+ WHERE pv.prevod_id = p.id
+ GROUP BY pv.prevod_id
+ ) AS vloge
+ FROM clan c
+ JOIN prevod p ON c.id = p.id_clan
+ JOIN jezik iz ON p.iz_id = iz.id
+ JOIN jezik v ON p.v_id = v.id
+ WHERE ${where}`,
+ params,
+ (err, rows) => {
+ if (err) {
+ console.error("Search error:", err);
+ return res.sendStatus(500);
}
+ if (!rows.length) return res.send({});
let out = [];
for (let row of rows) {
if (out.length <= 0 || out[out.length - 1].id !== row.id) {
out.push({
- "id": row.id,
- "ime": row.ime,
- "priimek": row.priimek,
- "spol": row.spol,
- "telefondoma": row.telefondoma,
- "telefonmobi": row.telefonmobi,
- "telefonsluzba": row.telefonsluzba,
- "email": row.email,
- "spletnastran": row.spletnastran,
- "prevodi": [],
+ id: row.id,
+ ime: row.ime,
+ priimek: row.priimek,
+ spol: row.spol,
+ telefondoma: row.telefondoma,
+ telefonmobi: row.telefonmobi,
+ telefonsluzba: row.telefonsluzba,
+ email: row.email,
+ spletnastran: row.spletnastran,
+ prevodi: [],
});
}
let podrocja = row.podrocja?.split(';') || [];
let vloge = row.vloge?.split(';') || [];
+
if (iskanjePodrocja.size) {
- podrocja = podrocja.filter((el) => iskanjePodrocja.has(el));
- if (podrocja.length === 0) continue;
+ podrocja = podrocja.filter(el => iskanjePodrocja.has(el));
+ if (!podrocja.length) continue;
}
if (iskanjeVloge.size) {
- vloge = vloge.filter((el) => iskanjeVloge.has(el));
- if (vloge.length === 0) continue;
+ vloge = vloge.filter(el => iskanjeVloge.has(el));
+ if (!vloge.length) continue;
}
-
out[out.length - 1].prevodi.push({
- "rang": row.rang,
- "iz": row.iz,
- "v": row.v,
- "strokovnopodrocje": row.podrocja?.split(';'),
- "vloga": row.vloge?.split(';'),
+ rang: row.rang,
+ iz: row.iz,
+ v: row.v,
+ strokovnopodrocje: row.podrocja?.split(';') || [],
+ vloga: row.vloge?.split(';') || [],
});
}
- out = out.filter((row) => row.prevodi.length > 0);
+ out = out.filter(row => row.prevodi.length > 0);
res.send(JSON.stringify(out, null, 4));
- });
-
+ }
+ );
});
app.get("/express_backend/api/get/user/:user_id", (req, res) => {
- db.query(`SELECT c.id as 'id' , c.ime as 'ime', c.priimek as 'priimek', c.spol as 'spol', c.telefondoma as 'telefondoma', c.telefonsluzba as 'telefonsluzba',
- c.telefonmobi as 'telefonmobi', c.email as 'email', c.spletnastran as 'spletnastran',
- p.rang as 'rang', iz.jezik as iz, v.jezik as v,
- (
- SELECT GROUP_CONCAT(po.podrocje SEPARATOR ';')
- FROM prevod_podrocje as pp
- JOIN podrocje as po
- ON pp.podrocje_id = po.id
- WHERE pp.prevod_id = p.id
- GROUP BY pp.prevod_id
- ) as 'podrocja',
- (
- SELECT GROUP_CONCAT(vl.vloga SEPARATOR ';')
- FROM prevod_vloga as pv
- JOIN vloga as vl
- ON pv.vloga_id = vl.id
- WHERE pv.prevod_id = p.id
- GROUP BY pv.prevod_id
- ) as 'vloge'
-FROM clan as c
-JOIN prevod as p
-ON c.id = p.id_clan
-JOIN jezik as iz
-ON p.iz_id = iz.id
-JOIN jezik as v
-ON p.v_id = v.id
-WHERE c.id = ?
-WHERE c.objava_v_iskalniku = 1
-ORDER BY rang;`, [req.params.user_id], (err, rows, fields)=> {
- if (err) throw err;
- if (rows.length === 0) {
- res.send({});
- return;
+ db.query(
+ `SELECT
+ c.id AS id, c.ime AS ime, c.priimek AS priimek, c.spol AS spol,
+ c.telefondoma AS telefondoma, c.telefonsluzba AS telefonsluzba,
+ c.telefonmobi AS telefonmobi, c.email AS email, c.spletnastran AS spletnastran,
+ p.id AS id_prevod, p.rang AS rang, iz.jezik AS iz, v.jezik AS v,
+ (
+ SELECT GROUP_CONCAT(po.podrocje SEPARATOR ';')
+ FROM prevod_podrocje pp
+ JOIN podrocje po ON pp.podrocje_id = po.id
+ WHERE pp.prevod_id = p.id
+ GROUP BY pp.prevod_id
+ ) AS podrocja,
+ (
+ SELECT GROUP_CONCAT(vl.vloga SEPARATOR ';')
+ FROM prevod_vloga pv
+ JOIN vloga vl ON pv.vloga_id = vl.id
+ WHERE pv.prevod_id = p.id
+ GROUP BY pv.prevod_id
+ ) AS vloge
+ FROM clan c
+ JOIN prevod p ON c.id = p.id_clan
+ JOIN jezik iz ON p.iz_id = iz.id
+ JOIN jezik v ON p.v_id = v.id
+ WHERE c.id = ? AND c.objava_v_iskalniku = 1
+ ORDER BY p.rang`,
+ [req.params.user_id],
+ (err, rows) => {
+ if (err) {
+ console.error("get/user error:", err);
+ return res.sendStatus(500);
+ }
+ if (!rows.length) return res.send({});
+
+ const first = rows[0];
+ const out = {
+ id: first.id,
+ ime: first.ime,
+ priimek: first.priimek,
+ telefondoma: first.telefondoma,
+ telefonmobi: first.telefonmobi,
+ telefonsluzba: first.telefonsluzba,
+ email: first.email,
+ spletnastran: first.spletnastran,
+ prevodi: [],
+ };
+ for (let row of rows) {
+ out.prevodi.push({
+ id: row.id_prevod,
+ rang: row.rang,
+ iz: row.iz,
+ v: row.v,
+ strokovnopodrocje: row.podrocja?.split(';') || [],
+ vloga: row.vloge?.split(';') || [],
+ });
+ }
+ res.send(JSON.stringify(out, null, 4));
}
-
- const first = rows[0];
-
- let out = {
- "id": first.id,
- "ime": first.ime,
- "priimek": first.priimek,
- "telefondoma": first.telefondoma,
- "telefonmobi": first.telefonmobi,
- "telefonsluzba": first.telefonsluzba,
- "email": first.email,
- "spletnastran": first.spletnastran,
- "prevodi": [],
- };
- for (let row of rows) {
- out.prevodi.push({
- "id": row.id_prevod,
- "rang": row.rang,
- "iz": row.iz,
- "v": row.v,
- "strokovnopodrocje": row.podrocja?.split(';'),
- "vloga": row.vloge?.split(';'),
- });
- }
- res.send(JSON.stringify(out, null, 4));
- });
-
+ );
});
app.get("/express_backend/api/getAll", (req, res) => {
- db.query(`SELECT c.id as 'id' , c.ime as 'ime', c.priimek as 'priimek', c.spol as 'spol', c.telefondoma as 'telefondoma', c.telefonsluzba as 'telefonsluzba',
- c.telefonmobi as 'telefonmobi', c.email as 'email', c.spletnastran as 'spletnastran',
- p.rang as 'rang', iz.jezik as iz, v.jezik as v,
- (
- SELECT GROUP_CONCAT(po.podrocje SEPARATOR ';')
- FROM prevod_podrocje as pp
- JOIN podrocje as po
- ON pp.podrocje_id = po.id
- WHERE pp.prevod_id = p.id
- GROUP BY pp.prevod_id
- ) as 'podrocja',
- (
- SELECT GROUP_CONCAT(vl.vloga SEPARATOR ';')
- FROM prevod_vloga as pv
- JOIN vloga as vl
- ON pv.vloga_id = vl.id
- WHERE pv.prevod_id = p.id
- GROUP BY pv.prevod_id
- ) as 'vloge'
-FROM clan as c
-JOIN prevod as p
-ON c.id = p.id_clan
-JOIN jezik as iz
-ON p.iz_id = iz.id
-JOIN jezik as v
-ON p.v_id = v.id
-WHERE c.objava_v_iskalniku = 1
-;`, (err, rows, fields)=> {
- if (err) throw err;
+ db.query(
+ `SELECT
+ c.id AS id, c.ime AS ime, c.priimek AS priimek, c.spol AS spol,
+ c.telefondoma AS telefondoma, c.telefonsluzba AS telefonsluzba,
+ c.telefonmobi AS telefonmobi, c.email AS email, c.spletnastran AS spletnastran,
+ p.rang AS rang, iz.jezik AS iz, v.jezik AS v,
+ (
+ SELECT GROUP_CONCAT(po.podrocje SEPARATOR ';')
+ FROM prevod_podrocje pp
+ JOIN podrocje po ON pp.podrocje_id = po.id
+ WHERE pp.prevod_id = p.id
+ GROUP BY pp.prevod_id
+ ) AS podrocja,
+ (
+ SELECT GROUP_CONCAT(vl.vloga SEPARATOR ';')
+ FROM prevod_vloga pv
+ JOIN vloga vl ON pv.vloga_id = vl.id
+ WHERE pv.prevod_id = p.id
+ GROUP BY pv.prevod_id
+ ) AS vloge
+ FROM clan c
+ JOIN prevod p ON c.id = p.id_clan
+ JOIN jezik iz ON p.iz_id = iz.id
+ JOIN jezik v ON p.v_id = v.id
+ WHERE c.objava_v_iskalniku = 1`,
+ (err, rows) => {
+ if (err) {
+ console.error("getAll error:", err);
+ return res.sendStatus(500);
+ }
- // Group prevodi per user
- let out = [];
- for (let row of rows) {
- if (out.length <= 0 || out[out.length - 1].id !== row.id) {
- out.push({
- "id": row.id,
- "ime": row.ime,
- "priimek": row.priimek,
- "spol": row.spol,
- "telefondoma": row.telefondoma,
- "telefonmobi": row.telefonmobi,
- "telefonsluzba": row.telefonsluzba,
- "email": row.email,
- "spletnastran": row.spletnastran,
- "prevodi": [],
+ // Group prevodi per user
+ let out = [];
+ for (let row of rows) {
+ if (out.length <= 0 || out[out.length - 1].id !== row.id) {
+ out.push({
+ id: row.id,
+ ime: row.ime,
+ priimek: row.priimek,
+ spol: row.spol,
+ telefondoma: row.telefondoma,
+ telefonmobi: row.telefonmobi,
+ telefonsluzba: row.telefonsluzba,
+ email: row.email,
+ spletnastran: row.spletnastran,
+ prevodi: [],
+ });
+ }
+ out[out.length - 1].prevodi.push({
+ rang: row.rang,
+ iz: row.iz,
+ v: row.v,
+ strokovnopodrocje: row.podrocja?.split(';') || [],
+ vloga: row.vloge?.split(';') || [],
});
}
- out[out.length - 1].prevodi.push({
- "rang": row.rang,
- "iz": row.iz,
- "v": row.v,
- "strokovnopodrocje": row.podrocja?.split(';'),
- "vloga": row.vloge?.split(';'),
- });
+ res.send(JSON.stringify(out, null, 4));
}
- res.send(JSON.stringify(out, null, 4));
- });
-})
+ );
+});
-
-// app.listen(PORT, () => {
-// console.log(`Server listening on port ${PORT}`);
-// });
\ No newline at end of file
+// app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); });