Compare commits

...

18 Commits

Author SHA1 Message Date
  ken add2ba0713 feat: add remaining components to dashboard 7 months ago
  ken 1a03253f64 Merge remote-tracking branch 'origin/Markeds/System-Banners' into dashboard-layout 7 months ago
  ken 4b93017aeb fix: remove element id 7 months ago
  ken d3e706f9c5 feat: add systemlink and marked pop-up component 7 months ago
  ken bfae070293 Merge remote-tracking branch 'refs/remotes/origin/markeds-pop-up' into dashboard-layout 7 months ago
  ken da42f8cf11 feat: add dashboard grid for other components 7 months ago
  johan 96eec70b26 Feat: Added pop-up which is executed when toggled/page load if wanted 7 months ago
  johan 723dfd87bb Feat: Added pop-up which is executed when page is loaded 7 months ago
  ken b070776dfe feat: overlaypopup on page load 7 months ago
  ken bd0d5501a1 feat: add overlaypopup, and move components in separate routes 7 months ago
  alan 2da5578c43 MarkedBanners + SystemBanners (Improved SystemBanners) 7 months ago
  ken 6b2ce3a76d fix: fix sizing and path 7 months ago
  alan 6b4611eaa3 MarkedBanners + SystemBanners 7 months ago
  ken ff9316a481 feat: add link on systemmessage component 7 months ago
  johan 33d2ff6049 Feat: Added page with basic styling for System Links component 7 months ago
  johan 5ebca991ab Feat: Added page with basic styling for System Links component 7 months ago
  ken 6a016b80b8 feat: add bootstrap lib 7 months ago
  ken ce0d7b0993 feat: add systemmessage component and page with all messages 7 months ago
43 changed files with 1357 additions and 356 deletions
Unified View
  1. +5
    -0
      .idea/codeStyles/codeStyleConfig.xml
  2. +1
    -0
      .idea/gca_admin.iml
  3. +21
    -0
      .idea/inspectionProfiles/Project_Default.xml
  4. +6
    -0
      .idea/jsLibraryMappings.xml
  5. +6
    -0
      .idea/prettier.xml
  6. +1
    -0
      gca-admin-gurusoft-message-dashboard/.env
  7. +1
    -1
      gca-admin-gurusoft-message-dashboard/README.md
  8. +11
    -6
      gca-admin-gurusoft-message-dashboard/index.html
  9. +23
    -1
      gca-admin-gurusoft-message-dashboard/package-lock.json
  10. +2
    -1
      gca-admin-gurusoft-message-dashboard/package.json
  11. +4
    -43
      gca-admin-gurusoft-message-dashboard/src/App.vue
  12. +30
    -30
      gca-admin-gurusoft-message-dashboard/src/assets/main.css
  13. +0
    -44
      gca-admin-gurusoft-message-dashboard/src/components/HelloWorld.vue
  14. +76
    -0
      gca-admin-gurusoft-message-dashboard/src/components/MarkedsBanner.vue
  15. +22
    -0
      gca-admin-gurusoft-message-dashboard/src/components/MarkedsBannerPage.vue
  16. +114
    -0
      gca-admin-gurusoft-message-dashboard/src/components/MarkedsPopUp.vue
  17. +44
    -0
      gca-admin-gurusoft-message-dashboard/src/components/OverlayPopup.vue
  18. +137
    -0
      gca-admin-gurusoft-message-dashboard/src/components/SystemBanner.vue
  19. +73
    -0
      gca-admin-gurusoft-message-dashboard/src/components/SystemBannerPage.vue
  20. +93
    -0
      gca-admin-gurusoft-message-dashboard/src/components/SystemLinks.vue
  21. +80
    -0
      gca-admin-gurusoft-message-dashboard/src/components/SystemMessagesPage.vue
  22. +85
    -0
      gca-admin-gurusoft-message-dashboard/src/components/SystemMesssages.vue
  23. +0
    -94
      gca-admin-gurusoft-message-dashboard/src/components/TheWelcome.vue
  24. +0
    -87
      gca-admin-gurusoft-message-dashboard/src/components/WelcomeItem.vue
  25. +0
    -7
      gca-admin-gurusoft-message-dashboard/src/components/icons/IconCommunity.vue
  26. +0
    -7
      gca-admin-gurusoft-message-dashboard/src/components/icons/IconDocumentation.vue
  27. +0
    -7
      gca-admin-gurusoft-message-dashboard/src/components/icons/IconEcosystem.vue
  28. +0
    -7
      gca-admin-gurusoft-message-dashboard/src/components/icons/IconSupport.vue
  29. +0
    -19
      gca-admin-gurusoft-message-dashboard/src/components/icons/IconTooling.vue
  30. +4
    -1
      gca-admin-gurusoft-message-dashboard/src/main.js
  31. +57
    -0
      gca-admin-gurusoft-message-dashboard/src/mocks/markedsBanner.json
  32. +62
    -0
      gca-admin-gurusoft-message-dashboard/src/mocks/marketPopUpMockData.json
  33. +0
    -0
      gca-admin-gurusoft-message-dashboard/src/mocks/mockdata.json
  34. +12
    -0
      gca-admin-gurusoft-message-dashboard/src/mocks/overlayPopup.json
  35. +35
    -0
      gca-admin-gurusoft-message-dashboard/src/mocks/systemBanner.json
  36. +62
    -0
      gca-admin-gurusoft-message-dashboard/src/mocks/systemLinksMockData.json
  37. +142
    -0
      gca-admin-gurusoft-message-dashboard/src/mocks/systemMessage.json
  38. +27
    -0
      gca-admin-gurusoft-message-dashboard/src/router/index.js
  39. +33
    -0
      gca-admin-gurusoft-message-dashboard/src/utils/api.js
  40. +41
    -0
      gca-admin-gurusoft-message-dashboard/src/views/Dashboard.vue
  41. +0
    -1
      gca-admin-gurusoft-message-dashboard/vite.config.js
  42. +42
    -0
      package-lock.json
  43. +5
    -0
      package.json

+ 5
- 0
.idea/codeStyles/codeStyleConfig.xml View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

+ 1
- 0
.idea/gca_admin.iml View File

@ -5,5 +5,6 @@
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="bootstrap" level="application" />
</component> </component>
</module> </module>

+ 21
- 0
.idea/inspectionProfiles/Project_Default.xml View File

@ -0,0 +1,21 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="7">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="router-link" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

+ 6
- 0
.idea/jsLibraryMappings.xml View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{bootstrap}" />
</component>
</project>

+ 6
- 0
.idea/prettier.xml View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

+ 1
- 0
gca-admin-gurusoft-message-dashboard/.env View File

@ -0,0 +1 @@
VITE_USE_MOCK=true

+ 1
- 1
gca-admin-gurusoft-message-dashboard/README.md View File

@ -1,4 +1,4 @@
# gca-admin-gurusoft-message-dashboard
npm # gca-admin-gurusoft-message-dashboard
This template should help get you started developing with Vue 3 in Vite. This template should help get you started developing with Vue 3 in Vite.

+ 11
- 6
gca-admin-gurusoft-message-dashboard/index.html View File

@ -1,13 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang=""> <html lang="">
<head>
<head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous"></script>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html> </html>

+ 23
- 1
gca-admin-gurusoft-message-dashboard/package-lock.json View File

@ -9,7 +9,8 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"bootstrap": "^5.3.7", "bootstrap": "^5.3.7",
"vue": "^3.5.17"
"vue": "^3.5.17",
"vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",
@ -1438,6 +1439,12 @@
"@vue/shared": "3.5.17" "@vue/shared": "3.5.17"
} }
}, },
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/devtools-core": { "node_modules/@vue/devtools-core": {
"version": "7.7.7", "version": "7.7.7",
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.7.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.7.tgz",
@ -2877,6 +2884,21 @@
} }
} }
}, },
"node_modules/vue-router": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
"integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

+ 2
- 1
gca-admin-gurusoft-message-dashboard/package.json View File

@ -10,7 +10,8 @@
}, },
"dependencies": { "dependencies": {
"bootstrap": "^5.3.7", "bootstrap": "^5.3.7",
"vue": "^3.5.17"
"vue": "^3.5.17",
"vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",

+ 4
- 43
gca-admin-gurusoft-message-dashboard/src/App.vue View File

@ -1,47 +1,8 @@
<script setup> <script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script> </script>
<template> <template>
<header>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
</div>
</header>
<main>
<TheWelcome />
</main>
</template>
<style scoped>
header {
line-height: 1.5;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
}
</style>
<div class="app">
<router-view/>
</div>
</template>

+ 30
- 30
gca-admin-gurusoft-message-dashboard/src/assets/main.css View File

@ -1,35 +1,35 @@
@import './base.css';
/*@import './base.css';*/
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
/*#app {*/
/* max-width: 1280px;*/
/* margin: 0 auto;*/
/* padding: 2rem;*/
/* font-weight: normal;*/
/*}*/
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
/*a,*/
/*.green {*/
/* text-decoration: none;*/
/* color: hsla(160, 100%, 37%, 1);*/
/* transition: 0.4s;*/
/* padding: 3px;*/
/*}*/
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
/*@media (hover: hover) {*/
/* a:hover {*/
/* background-color: hsla(160, 100%, 37%, 0.2);*/
/* }*/
/*}*/
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
/*@media (min-width: 1024px) {*/
/* body {*/
/* display: flex;*/
/* place-items: center;*/
/* }*/
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}
/* #app {*/
/* display: grid;*/
/* grid-template-columns: 1fr 1fr;*/
/* padding: 0 2rem;*/
/* }*/
/*}*/

+ 0
- 44
gca-admin-gurusoft-message-dashboard/src/components/HelloWorld.vue View File

@ -1,44 +0,0 @@
<script setup>
defineProps({
msg: {
type: String,
required: true,
},
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

+ 76
- 0
gca-admin-gurusoft-message-dashboard/src/components/MarkedsBanner.vue View File

@ -0,0 +1,76 @@
<template>
<div class="container mt-4">
<h2 class="mb-3">Markeds Banner</h2>
<div class="scroll-box border rounded p-3 mx-auto">
<div class="d-flex flex-row gap-3 scroll-content">
<div
v-for="banner in banners"
:key="banner.id"
class="card shadow-sm"
style="width: 16rem; flex: 0 0 auto; cursor: pointer"
@click="goToPage"
>
<img
:src="banner.image"
class="card-img-top"
:alt="banner.title"
style="height: 140px; object-fit: cover"
/>
<div class="card-body">
<h5 class="card-title">{{ banner.title }}</h5>
<p class="card-text text-muted" style="font-size: 0.85rem">
Type: {{ banner.type }}
</p>
<a
:href="banner.url"
class="btn btn-outline-primary btn-sm"
target="_blank"
>
til kampanje
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref, onMounted} from 'vue'
import {useRouter} from 'vue-router'
import mockdata from '../mocks/markedsBanner.json'
const banners = ref([])
const router = useRouter()
onMounted(() => {
banners.value = mockdata
})
const goToPage = () => {
router.push('/markedsbanners')
}
</script>
<style scoped>
.scroll-box {
max-width: 700px;
overflow-x: auto;
white-space: nowrap;
position: fixed;
}
.scroll-content {
flex-wrap: nowrap;
}
.scroll-box::-webkit-scrollbar {
height: 8px;
}
.scroll-box::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 4px;
}
</style>

+ 22
- 0
gca-admin-gurusoft-message-dashboard/src/components/MarkedsBannerPage.vue View File

@ -0,0 +1,22 @@
<template>
<div class="container mt-4">
<h2 class="mb-4">Alle markedsbannere</h2>
<div v-for="banner in banners" :key="banner.id" class="mb-4 border-bottom pb-3">
<h4>{{ banner.title }}</h4>
<img :src="banner.image" alt="banner" class="img-fluid" style="max-width: 300px"/>
<p class="text-muted">Type: {{ banner.type }}</p>
<a :href="banner.url" target="_blank" class="btn btn-sm btn-outline-secondary mt-2">Besøk kampanje</a>
</div>
</div>
</template>
<script setup>
import {ref, onMounted} from 'vue'
import mockdata from '../mocks/markedsBanner.json'
const banners = ref([])
onMounted(() => {
banners.value = mockdata
})
</script>

+ 114
- 0
gca-admin-gurusoft-message-dashboard/src/components/MarkedsPopUp.vue View File

@ -0,0 +1,114 @@
<script setup>
import {ref, onMounted, defineProps} from 'vue';
import popupData from '@/mocks/marketPopUpMockData.json';
const props = defineProps({
useMockedData: {
type: Boolean,
default: true
}
});
const popups = ref([]);
const currentPopup = ref(null);
const loading = ref(true);
const error = ref(null);
const showPopup = ref(false);
onMounted(async () => {
try {
if (props.useMockedData) {
// Use mock data
popups.value = popupData;
if (popups.value.length > 0) {
currentPopup.value = popups.value[4]; // Display first popup
}
loading.value = false;
} else {
// Fetch from API
const response = await fetch(`https://TODO-replace-with-API`);
if (!response.ok) {
throw new Error('Failed to fetch popup data');
}
popups.value = await response.json();
if (popups.value.length > 0) {
currentPopup.value = popups.value[0];
}
loading.value = false;
}
} catch (e) {
error.value = e.message;
loading.value = false;
}
});
const togglePopup = () => {
showPopup.value = !showPopup.value;
};
const closePopup = () => {
showPopup.value = false;
};
const handleAction = () => {
if (currentPopup.value?.url) {
window.open(currentPopup.value.url, '_blank');
}
closePopup();
};
</script>
<template>
<button v-if="!showPopup" @click="togglePopup" class="btn btn-primary">
Show Popup
</button>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else-if="showPopup && currentPopup"
class="position-fixed bottom-0 end-0 m-4 shadow-lg p-3 bg-white rounded border"
style="z-index: 1080; max-width: 350px;">
<!-- Image section -->
<div v-if="currentPopup.image" class="mb-3">
<img :src="currentPopup.image" alt="Campaign image" class="img-fluid rounded popup-image">
</div>
<h4 class="mb-1 text fw-bold">{{ currentPopup.title }}</h4>
<!-- Added ingress field -->
<p v-if="currentPopup.ingress" class="mb-1 fw-bold small">
{{ currentPopup.ingress }}
</p>
<p class="mb-2 small">
{{ currentPopup.description }}
</p>
<div class="d-flex justify-content-end gap-2">
<button @click="closePopup" class="btn btn-outline-secondary btn-sm">Lukk</button>
<button @click="handleAction" class="btn btn-primary btn-sm">
{{ currentPopup.linkText || 'Se mer' }}
</button>
</div>
</div>
</template>
<style scoped>
.shadow-lg {
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;
}
.rounded {
border-radius: 0.375rem !important;
}
.popup-image {
width: 100%;
object-fit: cover;
max-height: 180px;
}
</style>

+ 44
- 0
gca-admin-gurusoft-message-dashboard/src/components/OverlayPopup.vue View File

@ -0,0 +1,44 @@
<template>
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#overlayPopup">
Launch DEMO modal
</button>
<!-- Modal -->
<div class="modal" id="overlayPopup" tabindex="-1" aria-hidden="true" ref="overlayPopup">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content rounded-0">
<div class="modal-header justify-content-center text-center w-100 border-0">
<h1 class="modal-title fs-4 fw-bold w-100">{{ overlayInfo.title }}</h1>
<button type="button" class="btn-close position-absolute end-0 me-3" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body text-center">
{{ overlayInfo.description }}
</div>
<ul class="list-unstyled text-center pt-4">
<li v-for="(item, index) in overlayInfo.info" :key="index">
{{ item }}
</li>
</ul>
<div class="d-flex justify-content-center modal-footer border-0 pt-0">
<button type="button" class="btn btn-dark btn-lg w-25 rounded-0" data-bs-dismiss="modal">LUKK</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref, onMounted} from 'vue';
import {initializeApi} from '@/utils/api';
const overlayInfo = ref({})
const api = initializeApi();
onMounted(async () => {
overlayInfo.value = await api.getOverlayPopup();
});
</script>

+ 137
- 0
gca-admin-gurusoft-message-dashboard/src/components/SystemBanner.vue View File

@ -0,0 +1,137 @@
<template>
<div class="banner-container" v-if="activeBanners.length">
<div class="container">
<div class="alert-wrapper">
<div
v-for="banner in activeBanners.slice(0,1)"
:key="banner.id"
class="alert alert-danger border-danger banner-drop d-flex justify-content-between align-items-start shadow-sm small-banner"
role="alert"
@click="goToBannerPage"
style="cursor: pointer"
>
<div class="flex-grow-1">
<div class="d-flex flex-row justify-content-between align-items-center flex-wrap">
<span class="me-3">{{ banner.text }}</span>
<div class="text-end">
<a
v-if="banner.link"
:href="banner.link"
class="alert-link d-block"
target="_blank"
rel="noopener"
@click.stop
>
Mer info
</a>
<span class="badge bg-light text-dark mt-1 d-block">{{ banner.type }}</span>
</div>
</div>
<div class="text-muted mt-1" style="font-size: 0.75rem;">
{{ formatDate(banner.updated) }}
</div>
</div>
<button
type="button"
class="btn-close ms-3"
aria-label="Close"
@click.stop="banner.dismissed = true"
></button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref, onMounted, computed} from 'vue'
import {useRouter} from 'vue-router'
import mockdata from '../mocks/systemBanner.json'
const banners = ref([])
const router = useRouter()
const useMockedData = import.meta.env.VITE_USE_MOCK === 'true'
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:13000/api/systembanners'
onMounted(async () => {
try {
let data = []
if (useMockedData) {
data = mockdata
} else {
const response = await fetch(apiUrl)
if (!response.ok) throw new Error('API-feil')
data = await response.json()
}
banners.value = data.map((b) => ({
...b,
dismissed: false,
}))
} catch (error) {
console.warn('API feilet, bruker mockdata:', error)
banners.value = mockdata.map((b) => ({
...b,
dismissed: false,
}))
}
})
const activeBanners = computed(() =>
banners.value.filter((b) => !b.dismissed)
)
const formatDate = (iso) => {
const d = new Date(iso)
return d.toLocaleDateString('nb-NO', {
weekday: 'short',
day: '2-digit',
month: 'short',
hour: '2-digit',
minute: '2-digit',
})
}
const goToBannerPage = (event) => {
if (event.target.closest('a') || event.target.closest('button')) {
return
}
router.push('/SystemBannerPage')
}
</script>
<style scoped>
.banner-container {
background-color: #ffffff;
padding-top: 1rem;
padding-bottom: 1rem;
border-bottom: 2px solid #dc3545;
}
.alert-wrapper {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.small-banner {
padding: 0.5rem 1rem;
font-size: 0.9rem;
}
.banner-drop {
animation: dropDown 0.6s ease-out forwards;
opacity: 0;
transform: translateY(-20px);
}
@keyframes dropDown {
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

+ 73
- 0
gca-admin-gurusoft-message-dashboard/src/components/SystemBannerPage.vue View File

@ -0,0 +1,73 @@
<template>
<div class="container mt-4">
<h2 class="mb-4">System Banner Info</h2>
<div v-if="banners.length">
<div
v-for="banner in banners"
:key="banner.id"
class="alert alert-danger border-danger shadow-sm mb-3"
role="alert"
>
<h5 class="fw-bold">{{ banner.title }}</h5>
<p class="text-muted" style="font-size: 0.85rem;">
{{ formatDate(banner.updated) }}
</p>
<p>{{ banner.text }}</p>
<a
v-if="banner.link"
:href="banner.link"
class="alert-link d-block mt-2"
target="_blank"
rel="noopener"
>
Mer info
</a>
</div>
</div>
<div v-else>
<p class="text-muted">Ingen systemmeldinger tilgjengelig.</p>
</div>
</div>
</template>
<script setup>
import {ref, onMounted} from 'vue'
import mockdata from '../mocks/systemBanner.json'
const banners = ref([])
const useMockedData = import.meta.env.VITE_USE_MOCK === 'true'
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:13000/api/systembanners'
onMounted(async () => {
try {
let data = []
if (useMockedData) {
data = mockdata
} else {
const response = await fetch(apiUrl)
if (!response.ok) throw new Error('API-feil')
data = await response.json()
}
banners.value = data
} catch (error) {
console.warn('API feilet, bruker mockdata:', error)
banners.value = mockdata
}
})
const formatDate = (iso) => {
const d = new Date(iso)
return d.toLocaleDateString('nb-NO', {
weekday: 'short',
day: '2-digit',
month: 'short',
hour: '2-digit',
minute: '2-digit',
})
}
</script>

+ 93
- 0
gca-admin-gurusoft-message-dashboard/src/components/SystemLinks.vue View File

@ -0,0 +1,93 @@
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else class="container p-3 border overflow-auto" style="max-height: 340px; width: 420px;">
<div
v-for="(link, index) in links"
:key="index"
class="mb-2"
>
<a
:href="link.url"
class="btn d-flex align-items-center text-start w-100 px-4 py-3"
:class="getButtonClass(link.type)"
:style="{ borderRadius: buttonRadius }"
target="_blank"
>
<i :class="getIconClass(link.icon)" class="me-2"></i>
{{ link.text }}
</a>
</div>
</div>
</template>
<script setup>
import {ref, onMounted, defineProps} from 'vue';
import linkData from '@/mocks/systemLinksMockData.json';
const props = defineProps({
metrics: {
type: [String, Object, Array],
default: null
},
buttonRadius: {
type: String,
default: '0.0rem'
},
useMockedData: {
type: Boolean,
default: true
}
});
const links = ref([]);
const loading = ref(true);
const error = ref(null);
const getButtonClass = (type) => {
switch (type?.toLowerCase()) {
case 'internal':
return 'btn-secondary opacity-75';
case 'external':
return 'btn-success opacity-75';
case 'admin':
return 'btn-warning opacity-75';
case 'system':
return 'btn-light opacity-75';
default:
return 'btn-info opacity-75';
}
};
const getIconClass = (icon) => `bi bi-${icon}`;
onMounted(async () => {
try {
if (props.useMockedData) {
// Use mock
links.value = linkData;
loading.value = false;
} else {
// Fetch from API
const response = await fetch(`https://TODO-replace-with-API`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
links.value = await response.json();
}
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
});
</script>
<style scoped>
.container {
background-color: #f8f9fa;
border: 1px solid #ddd;
overflow-y: auto;
}
</style>

+ 80
- 0
gca-admin-gurusoft-message-dashboard/src/components/SystemMessagesPage.vue View File

@ -0,0 +1,80 @@
<template>
<div class="container-sm py-4">
<div class="mb-3">
<router-link to="/" class="btn btn-sm btn-outline-primary">
Tilbake
</router-link>
</div>
<h2 class="h3 fw-bold mb-3 pt-1">System meldinger</h2>
<ul v-if="messages.length" class="list-unstyled">
<li
v-for="msg in messages"
:key="msg.id"
class="border rounded p-3 mb-3"
>
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="fw-semibold">{{ msg.title }}</h5>
<span
class="badge text-dark text-uppercase"
:class="typeClass(msg.type)"
>
{{ msg.type }}
</span>
</div>
<p class="mb-1 small"><strong>Melding:</strong> {{ msg.message }}</p>
<p class="mb-1 small"><strong>Beskrivelse:</strong> {{ msg.description }}</p>
<p class="mb-1 text-muted small">{{ formatDate(msg.date) }}</p>
<div v-if="msg.url" class="mt-1">
<a
:href="msg.url"
target="_blank"
rel="noopener noreferrer"
class="link-primary small"
>
Les mer
</a>
</div>
</li>
</ul>
<p v-else class="text-muted small">Ingen meldinger tilgjengelig.</p>
</div>
</template>
<script setup>
import {onMounted, ref} from 'vue';
import {initializeApi} from '../utils/api.js';
const api = initializeApi();
const messages = ref([]);
onMounted(async () => {
messages.value = await api.getAllMessages();
});
function formatDate(date) {
const d = new Date(date);
const day = String(d.getDate()).padStart(2, '0');
const month = String(d.getMonth() + 1).padStart(2, '0');
const year = String(d.getFullYear()).slice(2); // get last 2 digits
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
return `${day}.${month}.${year} ${hours}:${minutes}`;
}
const typeClass = (type) => {
const typeMap = {
info: 'bg-success bg-opacity-50',
warning: 'bg-warning bg-opacity-75',
new: 'bg-primary bg-opacity-25',
};
return typeMap[type] || 'bg-secondary bg-opacity-50';
};
</script>

+ 85
- 0
gca-admin-gurusoft-message-dashboard/src/components/SystemMesssages.vue View File

@ -0,0 +1,85 @@
<template>
<div class="container">
<div class="card border-0">
<h4 class="d-flex justify-content-start m-3">System meldinger</h4>
<div class="card-body pt-0">
<ul v-if="messages.length" class="list-group list-group-flush pad gap-3">
<li
v-for="msg in messages.slice(0, 3)"
:key="msg.id"
class="list-group-item d-flex flex-column border border-1 border-dark"
:class="typeClass(msg.type)">
<div class="d-flex justify-content-between align-items-center w-100">
<span class="text-dark fw-bold fs-6">{{ msg.title }}</span>
<small class="text-black fw-bold fs-6">{{ formatDate(msg.date) }}</small>
</div>
<p class="mb-0">{{ msg.message }}</p>
<div v-if="msg.url">
<a
:href="msg.url"
target="_blank"
rel="noopener noreferrer"
class="link-primary small"
>
Les mer her
</a>
</div>
</li>
</ul>
<p v-else class="text-muted">Loading messages...</p>
<!-- Indicator for more messages -->
<div v-if="messages.length > 5" class="text-center text-muted fst-italic small mt-2">
...
</div>
<div class="text-center pt-1">
<router-link to="/systemmessagelist" class="text-primary text-decoration-underline">
Flere meldinger
</router-link>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref, onMounted} from 'vue';
import {initializeApi} from '../utils/api.js';
const api = initializeApi();
const messages = ref([]);
onMounted(async () => {
messages.value = await api.getAllMessages();
});
function formatDate(date) {
const d = new Date(date);
const day = String(d.getDate()).padStart(2, '0');
const month = String(d.getMonth() + 1).padStart(2, '0');
const year = String(d.getFullYear()).slice(2); // get last 2 digits
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
return `${day}.${month}.${year} ${hours}:${minutes}`;
}
const typeClass = (type) => {
const typeMap = {
info: 'bg-success bg-opacity-50',
warning: 'bg-warning bg-opacity-75',
new: 'bg-primary bg-opacity-25',
};
return typeMap[type] || 'bg-secondary bg-opacity-50';
};
</script>
<style>
.container {
max-width: 600px;
}
</style>

+ 0
- 94
gca-admin-gurusoft-message-dashboard/src/components/TheWelcome.vue View File

@ -1,94 +0,0 @@
<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
+
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
you need to test your components and web pages, check out
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
and
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
/
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
<br />
More instructions are available in
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
(our official Discord server), or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
>StackOverflow</a
>. You should also follow the official
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
Bluesky account or the
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
X account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>

+ 0
- 87
gca-admin-gurusoft-message-dashboard/src/components/WelcomeItem.vue View File

@ -1,87 +0,0 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

+ 0
- 7
gca-admin-gurusoft-message-dashboard/src/components/icons/IconCommunity.vue View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

+ 0
- 7
gca-admin-gurusoft-message-dashboard/src/components/icons/IconDocumentation.vue View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

+ 0
- 7
gca-admin-gurusoft-message-dashboard/src/components/icons/IconEcosystem.vue View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

+ 0
- 7
gca-admin-gurusoft-message-dashboard/src/components/icons/IconSupport.vue View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

+ 0
- 19
gca-admin-gurusoft-message-dashboard/src/components/icons/IconTooling.vue View File

@ -1,19 +0,0 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

+ 4
- 1
gca-admin-gurusoft-message-dashboard/src/main.js View File

@ -2,5 +2,8 @@ import './assets/main.css'
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router';
createApp(App).mount('#app')
createApp(App)
.use(router)
.mount('#app');

+ 57
- 0
gca-admin-gurusoft-message-dashboard/src/mocks/markedsBanner.json View File

@ -0,0 +1,57 @@
[
{
"id": 4,
"createdBy": 123,
"created": "2025-06-25T10:27:34Z",
"updatedBy": 124,
"updated": "2025-06-25T10:27:34Z",
"title": "Sommersalg",
"url": "https://example.com/salg",
"image": "https://providavarmeshop.no/wp-content/uploads/2023/06/sommersalg-tekst-300x137.png",
"type": "PROMO"
},
{
"id": 5,
"createdBy": 123,
"created": "2025-06-25T10:27:34Z",
"updatedBy": 124,
"updated": "2025-06-25T10:27:34Z",
"title": "Ny produktlansering",
"url": "https://example.com/nytt-produkt",
"image": "https://providavarmeshop.no/wp-content/uploads/2023/06/sommersalg-tekst-300x137.png",
"type": "PROMO"
},
{
"id": 6,
"createdBy": 123,
"created": "2025-06-25T10:27:34Z",
"updatedBy": 124,
"updated": "2025-06-25T10:27:34Z",
"title": "Kundelojalitetsprogram",
"url": "https://example.com/kundelojalitet",
"image": "https://providavarmeshop.no/wp-content/uploads/2023/06/sommersalg-tekst-300x137.png",
"type": "PROMO"
},
{
"id": 7,
"createdBy": 123,
"created": "2025-06-25T10:27:34Z",
"updatedBy": 124,
"updated": "2025-06-25T10:27:34Z",
"title": "Kundeservice tilgjengelig",
"url": "https://example.com/kundeservice",
"image": "https://providavarmeshop.no/wp-content/uploads/2023/06/sommersalg-tekst-300x137.png",
"type": "PROMO"
},
{
"id": 8,
"createdBy": 123,
"created": "2025-06-25T10:27:34Z",
"updatedBy": 124,
"updated": "2025-06-25T10:27:34Z",
"title": "Vår nye nettbutikk er lansert!",
"url": "https://example.com/ny-nettbutikk",
"image": "https://providavarmeshop.no/wp-content/uploads/2023/06/sommersalg-tekst-300x137.png",
"type": "PROMO"
}
]

+ 62
- 0
gca-admin-gurusoft-message-dashboard/src/mocks/marketPopUpMockData.json View File

@ -0,0 +1,62 @@
[
{
"title": "Sommertilbud!",
"ingress": "Få opptil 50% rabatt",
"description": "Spar stort på sommerens mest populære varer. Tilbudet varer ut uken.Spar stort på sommerens mest populære varer. Tilbudet varer ut ukenSpar stort på sommerens mest populære varer. Tilbudet varer ut ukenSpar stort på sommerens mest populære varer. Tilbudet varer ut ukenSpar stort på sommerens mest populære varer. Tilbudet varer ut ukenSpar stort på sommerens mest populære varer. Tilbudet varer ut ukenSpar stort på sommerens mest populære varer. Tilbudet varer ut uken",
"url": "https://google.com",
"linkText": "Les mer",
"image": "https://yavuzceliker.github.io/sample-images/image-92.jpg",
"type": "campaign",
"date": "2025-06-27"
},
{
"title": "Ny funksjon i appen",
"ingress": "Chat med oss direkte",
"description": "Vi har lansert en ny chat-funksjon hvor du kan få hjelp på sekunder.",
"url": "https://example.com/chat-funksjon",
"linkText": "Se hvordan",
"image": "https://yavuzceliker.github.io/sample-images/image-632.jpg",
"type": "feature",
"date": "2025-06-26"
},
{
"title": "Viktig informasjon",
"ingress": "Endringer i bruksvilkår",
"description": "Vi har oppdatert våre vilkår og personvernregler. Les mer om endringene her.",
"url": "https://example.com/vilkar",
"linkText": "Les nye vilkår",
"image": "https://yavuzceliker.github.io/sample-images/image-1.jpg",
"type": "notice",
"date": "2025-06-25"
},
{
"title": "Bli med på undersøkelse",
"ingress": "Hjelp oss å bli bedre",
"description": "Ta vår 2-minutters undersøkelse og vær med i trekningen av gavekort.",
"url": "https://example.com/undersokelse",
"linkText": "Svar nå",
"image": "https://yavuzceliker.github.io/sample-images/image-312.jpg",
"type": "survey",
"date": "2025-06-24"
},
{
"title": "Vi er her for deg",
"ingress": "Ny kundeserviceportal",
"description": "Oppdag vårt nye hjelpesenter med artikler, guider og live-hjelp.",
"url": "https://example.com/hjelp",
"linkText": "Gå til hjelpesenter",
"image": "https://yavuzceliker.github.io/sample-images/image-460.jpg",
"type": "support",
"date": "2025-06-23"
},
{
"title": "Eksklusivt webinartilbud",
"ingress": "Lær av ekspertene",
"description": "Bli med på vårt gratis webinar om digital markedsføring. Begrenset med plasser.",
"url": "https://example.com/webinar",
"linkText": "Meld deg på",
"image": "https://yavuzceliker.github.io/sample-images/image-699.jpg",
"type": "event",
"date": "2025-06-22"
}
]

+ 0
- 0
gca-admin-gurusoft-message-dashboard/src/mocks/mockdata.json View File


+ 12
- 0
gca-admin-gurusoft-message-dashboard/src/mocks/overlayPopup.json View File

@ -0,0 +1,12 @@
{
"title": "Driftsmelding e-handel",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"footer": "Vennlig hilsen",
"info": [
"Gurusoft AS",
"+47 123 45 678",
"support.ecommerce@gurusoft.no"
]
}

+ 35
- 0
gca-admin-gurusoft-message-dashboard/src/mocks/systemBanner.json View File

@ -0,0 +1,35 @@
[
{
"id": 1,
"createdBy": 123,
"created": "2025-06-25T10:27:34Z",
"updatedBy": 124,
"updated": "2025-06-25T10:27:34Z",
"title": "Velkommen til systemet",
"text": "Dette er en viktig melding fra systemet.",
"link": "https://example.com/info",
"type": "WARNING"
},
{
"id": 2,
"createdBy": 123,
"created": "2025-06-25T10:27:34Z",
"updatedBy": 124,
"updated": "2025-06-25T10:27:34Z",
"title": "Systemvedlikehold",
"text": "Systemet vil være utilgjengelig fra 12:00 til 14:00.",
"link": "https://example.com/maintenance",
"type": "INFO"
},
{
"id": 3,
"createdBy": 123,
"created": "2025-06-25T10:27:34Z",
"updatedBy": 124,
"updated": "2025-06-25T10:27:34Z",
"title": "Feil i systemet",
"text": "Det har oppstått en feil i systemet. Vennligst kontakt support.",
"link": null,
"type": "ERROR"
}
]

+ 62
- 0
gca-admin-gurusoft-message-dashboard/src/mocks/systemLinksMockData.json View File

@ -0,0 +1,62 @@
[
{
"url": "https://report.gurusoft.no/",
"text": "Gurusoft hjemmeside",
"type": "admin",
"icon": "dashboard"
},
{
"url": "https://www.gurusoft.no/ta-kontakt?utm_term=gurusoft&utm_campaign=Search+-+Gurusoft+-+Brand&utm_source=adwords&utm_medium=ppc&hsa_acc=1671275617&hsa_cam=21429497412&hsa_grp=164723437872&hsa_ad=704644852789&hsa_src=g&hsa_tgt=kwd-357983112442&hsa_kw=gurusoft&hsa_mt=b&hsa_net=adwords&hsa_ver=3&gad_source=1&gad_campaignid=21429497412&gbraid=0AAAAAC6-h5QA-Uem3fnf9a8Q7l0FhCFkF&gclid=CjwKCAjw3_PCBhA2EiwAkH_j4oYc5_RLO87_7JXoxRBwBR3sR7mHymCM5WRcyM_ORAb7kcmDvoi-HBoCxR4QAvD_BwE",
"text": "Kontakt oss",
"type": "internal",
"icon": "book"
},
{
"url": "https://open.spotify.com/show/4iPq4aLAWHbsXpg0dKSGnY?si=c9ae69117a5740fd&nd=1&dlsi=328d8504668c4558",
"text": "Musikk innslag",
"type": "internal",
"icon": "support"
},
{
"url": "https://www.company.com/privacy-policy",
"text": "Privacy Policy",
"type": "external",
"icon": "policy"
},
{
"url": "https://www.company.com/terms-of-service",
"text": "Terms of Service",
"type": "what",
"icon": "terms"
},
{
"url": "https://intranet.company.local/dashboard",
"text": "Admin Dashboard",
"type": "admin",
"icon": "dashboard"
},
{
"url": "https://google.com",
"text": "Knowledge Base",
"type": "internal",
"icon": "book"
},
{
"url": "https://intranet.company.local/support",
"text": "Support",
"type": "internal",
"icon": "support"
},
{
"url": "https://www.company.com/privacy-policy",
"text": "Privacy Policy",
"type": "external",
"icon": "policy"
},
{
"url": "https://www.company.com/terms-of-service",
"text": "Terms of Service",
"type": "what",
"icon": "terms"
}
]

+ 142
- 0
gca-admin-gurusoft-message-dashboard/src/mocks/systemMessage.json View File

@ -0,0 +1,142 @@
[
{
"id": 1,
"type": "info",
"title": "System Maintenance Notification",
"message": "Scheduled maintenance will occur this weekend.",
"description": "The system will be down for maintenance from 10 PM to 2 AM on Saturday.",
"url": "https://google.com",
"tags": [
"maintenance",
"downtime",
"system"
],
"date": "2025-06-28T22:00:00Z"
},
{
"id": 2,
"type": "warning",
"title": "High Memory Usage Detected",
"message": "One of the servers is experiencing high memory usage.",
"description": "Our monitoring tools have detected abnormal memory usage on node-03. Investigation is ongoing.",
"url": "https://status.example.com/memory-warning",
"tags": [
"performance",
"alert",
"memory"
],
"date": "2025-06-26T08:45:00Z"
},
{
"id": 3,
"type": "new",
"title": "Database Connection Failure",
"message": "The application failed to connect to the database.",
"description": "An issue with the DB connection pool is preventing new sessions. Engineers are working on a fix.",
"url": "https://status.example.com/db-error",
"tags": [
"database",
"error",
"critical"
],
"date": "2025-06-25T14:10:00Z"
},
{
"id": 4,
"type": "new",
"title": "New Feature Release",
"message": "We’ve launched a new dashboard experience.",
"description": "The new dashboard includes improved analytics and faster loading times. Available to all users.",
"url": "https://docs.example.com/new-dashboard",
"tags": [
"release",
"feature",
"dashboard"
],
"date": "2025-06-24T10:00:00Z"
},
{
"id": 5,
"type": "random",
"title": "API Rate Limit Notice",
"message": "Your application is approaching the rate limit.",
"description": "Please optimize your API usage to avoid temporary blocks. Visit our docs for best practices.",
"url": "https://api.example.com/docs/rate-limiting",
"tags": [
"api",
"rate-limit",
"usage"
],
"date": "2025-06-23T17:30:00Z"
},
{
"id": 6,
"type": "info",
"title": "System Maintenance Notification",
"message": "Scheduled maintenance will occur this weekend.",
"description": "The system will be down for maintenance from 10 PM to 2 AM on Saturday.",
"url": "https://status.example.com/maintenance",
"tags": [
"maintenance",
"downtime",
"system"
],
"date": "2025-06-28T22:00:00Z"
},
{
"id": 7,
"type": "warning",
"title": "High Memory Usage Detected",
"message": "One of the servers is experiencing high memory usage.",
"description": "Our monitoring tools have detected abnormal memory usage on node-03. Investigation is ongoing.",
"url": "https://status.example.com/memory-warning",
"tags": [
"performance",
"alert",
"memory"
],
"date": "2025-06-26T08:45:00Z"
},
{
"id": 8,
"type": "new",
"title": "Database Connection Failure",
"message": "The application failed to connect to the database.",
"description": "An issue with the DB connection pool is preventing new sessions. Engineers are working on a fix.",
"url": "https://status.example.com/db-error",
"tags": [
"database",
"error",
"critical"
],
"date": "2025-06-25T14:10:00Z"
},
{
"id": 9,
"type": "new",
"title": "New Feature Release",
"message": "We’ve launched a new dashboard experience.",
"description": "The new dashboard includes improved analytics and faster loading times. Available to all users.",
"url": "https://docs.example.com/new-dashboard",
"tags": [
"release",
"feature",
"dashboard"
],
"date": "2025-06-24T10:00:00Z"
},
{
"id": 10,
"type": "random",
"title": "API Rate Limit Notice",
"message": "Your application is approaching the rate limit.",
"description": "Please optimize your API usage to avoid temporary blocks. Visit our docs for best practices.",
"url": "https://api.example.com/docs/rate-limiting",
"tags": [
"api",
"rate-limit",
"usage"
],
"date": "2025-06-23T17:30:00Z"
}
]

+ 27
- 0
gca-admin-gurusoft-message-dashboard/src/router/index.js View File

@ -0,0 +1,27 @@
import {createRouter, createWebHistory} from 'vue-router';
import SystemMessagesPage from '../components/SystemMessagesPage.vue';
import SystemMessages from '../components/SystemMesssages.vue';
import Dashboard from '../views/Dashboard.vue';
import SystemLinks from '../components/SystemLinks.vue'
import SystemBannerPage from '../components/SystemBannerPage.vue'
import SystemBanner from '../components/SystemBanner.vue'
import MarkedsBannerPage from '../components/MarkedsBannerPage.vue'
import MarkedsBanner from '../components/MarkedsBanner.vue'
const routes = [
{path: '/systemmessage', name: 'SystemMessage', component: SystemMessages},
{path: '/systemmessagelist', name: 'SystemMessageList', component: SystemMessagesPage},
{path: '/systemlinks', name: 'SystemLinks', component: SystemLinks},
{path: '/', name: 'Dashboard', component: Dashboard},
{path: '/systembanner', name: 'Home', component: SystemBanner},
{path: '/systembannerpage', name: 'BannerPage', component: SystemBannerPage},
{path: '/markedsbanners', name: 'MarkedsPage', component: MarkedsBannerPage},
{path: '/markedsbanner', name: 'MarkedsStart', component: MarkedsBanner}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;

+ 33
- 0
gca-admin-gurusoft-message-dashboard/src/utils/api.js View File

@ -0,0 +1,33 @@
import mockSystemMessages from '../mocks/systemMessage.json';
import mockOverlay from '../mocks/overlayPopup.json';
const createMockApi = () => {
return {
// Get all messages
getAllMessages: async () => {
await new Promise(resolve => setTimeout(resolve, 300));
return mockSystemMessages;
},
getOverlayPopup: async () => {
await new Promise(resolve => setTimeout(resolve, 300));
return mockOverlay;
},
// Get a single message by ID
getMessageById: async (id) => {
await new Promise(resolve => setTimeout(resolve, 200));
return mockSystemMessages.find(msg => msg.id === id) || null;
},
// Get messages filtered by type (e.g. INFO, ERROR)
getMessagesByType: async (type) => {
await new Promise(resolve => setTimeout(resolve, 200));
return mockSystemMessages.filter(msg => msg.type === type);
}
};
};
export const initializeApi = () => {
return createMockApi();
};

+ 41
- 0
gca-admin-gurusoft-message-dashboard/src/views/Dashboard.vue View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import OverlayPopup from "../components/OverlayPopup.vue";
import SystemMesssages from "../components/SystemMesssages.vue";
import SystemLinks from "../components/SystemLinks.vue";
import MarkedsPopUp from "../components/MarkedsPopUp.vue";
import SystemBanner from "@/components/SystemBanner.vue";
import MarkedsBanner from "@/components/MarkedsBanner.vue";
</script>
<style scoped>
</style>
<template>
<div class="container-fluid text-center border border-3 border-dark">
<div class="row border">
<div class="col">
<SystemBanner/>
</div>
</div>
<div class="row">
<div class="col border">
<MarkedsBanner/>
</div>
<div class="col border">
<div class="row">
<div class="col">
<SystemMesssages/>
</div>
<div class="col border border-warning">
<SystemLinks/>
</div>
</div>
</div>
</div>
</div>
<OverlayPopup/>
<MarkedsPopUp/>
</template>

+ 0
- 1
gca-admin-gurusoft-message-dashboard/vite.config.js View File

@ -4,7 +4,6 @@ import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools' import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),

+ 42
- 0
package-lock.json View File

@ -0,0 +1,42 @@
{
"name": "sommer2025",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"bootstrap": "^5.3.7"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/bootstrap": {
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
"integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
}
}
}

+ 5
- 0
package.json View File

@ -0,0 +1,5 @@
{
"dependencies": {
"bootstrap": "^5.3.7"
}
}

Loading…
Cancel
Save