Initial commit of testapi

This commit is contained in:
2025-06-20 13:33:07 +02:00
commit a455aba1d5
377 changed files with 30566 additions and 0 deletions
+148
View File
@@ -0,0 +1,148 @@
<script setup lang="ts">
import { computed } from 'vue';
import AppLayout from '@/layouts/AppLayout.vue';
import { type BreadcrumbItem } from '@/types';
import { Head, Link, useForm, usePage } from '@inertiajs/vue3';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { Plus } from 'lucide-vue-next';
interface Profile { id: string; name: string };
interface Department { id: number; name: string };
const page = usePage<{
profiles: Profile[]
departments: Department[]
}>();
const profiles = computed(() => page.props.profiles);
const departments = computed(() => page.props.departments);
const form = useForm({
project_name: '',
project_number: '',
user_ids: [] as string[],
department_ids:[] as number[],
});
// toggle a profile id in the user_ids array
function toggleUser(id: string, checked: boolean) {
if (checked) {
form.user_ids.push(id);
} else {
form.user_ids = form.user_ids.filter(x => x !== id);
}
}
// toggle a department id in the department_ids array
function toggleDept(id: number, checked: boolean) {
if (checked) {
form.department_ids.push(id);
} else {
form.department_ids = form.department_ids.filter(x => x !== id);
}
}
const breadcrumbs: BreadcrumbItem[] = [
{ title: 'Dashbord', href: '/dashboard' },
{ title: 'Prosjekter', href: '/smartdok/projects' },
{ title: 'Opprett', href: '/smartdok/projects/create' },
];
function submit() {
form.post(route('smartdok.projects.store'));
}
</script>
<template>
<Head title="Opprett prosjekt" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="mb-6 flex items-center justify-between p-6">
<h3 class="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-3xl">
Opprett prosjekt
</h3>
</div>
<div class="flex justify-center">
<form
@submit.prevent="submit"
class="space-y-6 min-w-2xl lg:min-w-3xl xl:min-w-4xl"
>
<!-- Prosjektnavn -->
<div>
<Label for="project_name">Prosjektnavn</Label>
<Input
id="project_name"
v-model="form.project_name"
placeholder="Skriv inn prosjektnavn"
class="mt-2"
/>
<p v-if="form.errors.project_name" class="mt-1 text-sm text-red-600">
{{ form.errors.project_name }}
</p>
</div>
<!-- Prosjektnummer -->
<div>
<Label for="project_number">Prosjektnummer</Label>
<Input
id="project_number"
v-model="form.project_number"
placeholder="Skriv inn prosjektnummer"
class="mt-2"
/>
<p v-if="form.errors.project_number" class="mt-1 text-sm text-red-600">
{{ form.errors.project_number }}
</p>
</div>
<!-- Tilknyttede brukere -->
<div>
<Label>Tilknyttede brukere</Label>
<div class="mt-2 space-y-1">
<div
v-for="profile in profiles"
:key="profile.id"
class="flex items-center space-x-2"
>
<Checkbox
:model-value="form.user_ids.includes(profile.id)"
@update:modelValue="checked => toggleUser(profile.id, checked)"
/>
<span>{{ profile.name }}</span>
</div>
</div>
<p v-if="form.errors.user_ids" class="mt-1 text-sm text-red-600">
{{ form.errors.user_ids }}
</p>
</div>
<!-- Tilknyttede avdelinger -->
<div>
<Label>Tilknyttede avdelinger</Label>
<div class="mt-2 space-y-1">
<div
v-for="dept in departments"
:key="dept.id"
class="flex items-center space-x-2"
>
<Checkbox
:model-value="form.department_ids.includes(dept.id)"
@update:modelValue="checked => toggleDept(dept.id, checked)"
/>
<span>{{ dept.name }}</span>
</div>
</div>
<p v-if="form.errors.department_ids" class="mt-1 text-sm text-red-600">
{{ form.errors.department_ids }}
</p>
</div>
<!-- Actions -->
<div class="flex items-center justify-end space-x-4">
<Link :href="route('smartdok.projects.index')">
<Button variant="outline">Avbryt</Button>
</Link>
<Button type="submit" :disabled="form.processing">Opprett</Button>
</div>
</form>
</div>
</AppLayout>
</template>
+159
View File
@@ -0,0 +1,159 @@
<script setup lang="ts">
import { computed } from 'vue';
import AppLayout from '@/layouts/AppLayout.vue';
import { Head, Link, usePage, useForm } from '@inertiajs/vue3';
import type { BreadcrumbItem } from '@/types';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
interface Profile { id: string; name: string };
interface Department { id: number; name: string };
interface Project {
id: number
project_name: string
project_number: string
user_ids: string[]
department_ids: number[]
};
const page = usePage<{
project: Project
profiles: Profile[]
departments: Department[]
}>();
const project = computed(() => page.props.project);
const profilesList = computed(() => page.props.profiles);
const deptsList = computed(() => page.props.departments);
const form = useForm({
project_name: project.value.project_name,
project_number: project.value.project_number,
user_ids: project.value.user_ids,
department_ids: project.value.department_ids,
});
function toggleUser(id: string, checked: boolean) {
form.user_ids = checked
? [...form.user_ids, id]
: form.user_ids.filter(x => x !== id);
}
function toggleDept(id: number, checked: boolean) {
form.department_ids = checked
? [...form.department_ids, id]
: form.department_ids.filter(x => x !== id);
}
const breadcrumbs = computed<BreadcrumbItem[]>(() => [
{ title: 'Dashbord', href: '/dashboard' },
{ title: 'Prosjekter', href: '/smartdok/projects' },
{
title: 'Rediger',
href: route('smartdok.projects.edit', project.value.id),
},
]);
function submit() {
form.put(route('smartdok.projects.update', project.value.id));
}
</script>
<template>
<Head title="Rediger prosjekt" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="p-6 space-y-8">
<!-- Header -->
<div class="flex items-center justify-between">
<h3 class="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-3xl">
Rediger prosjekt
</h3>
</div>
<!-- Centered form -->
<div class="flex justify-center items-center">
<form
@submit.prevent="submit"
class="space-y-6 min-w-2xl lg:min-w-3xl xl:min-w-4xl"
>
<!-- Prosjektnavn -->
<div>
<Label for="project_name">Prosjektnavn</Label>
<Input
id="project_name"
v-model="form.project_name"
placeholder="Skriv inn prosjektnavn"
class="mt-2"
/>
<p v-if="form.errors.project_name" class="mt-1 text-sm text-red-600">
{{ form.errors.project_name }}
</p>
</div>
<!-- Prosjektnummer -->
<div>
<Label for="project_number">Prosjektnummer</Label>
<Input
id="project_number"
v-model="form.project_number"
placeholder="Skriv inn prosjektnummer"
class="mt-2"
/>
<p v-if="form.errors.project_number" class="mt-1 text-sm text-red-600">
{{ form.errors.project_number }}
</p>
</div>
<!-- Tilknyttede brukere -->
<div>
<Label>Tilknyttede brukere</Label>
<div class="mt-2 space-y-1">
<div
v-for="p in profilesList"
:key="p.id"
class="flex items-center space-x-2"
>
<Checkbox
:model-value="form.user_ids.includes(p.id)"
@update:modelValue="checked => toggleUser(p.id, checked)"
/>
<span>{{ p.name }}</span>
</div>
</div>
<p v-if="form.errors.user_ids" class="mt-1 text-sm text-red-600">
{{ form.errors.user_ids }}
</p>
</div>
<!-- Tilknyttede avdelinger -->
<div>
<Label>Tilknyttede avdelinger</Label>
<div class="mt-2 space-y-1">
<div
v-for="d in deptsList"
:key="d.id"
class="flex items-center space-x-2"
>
<Checkbox
:model-value="form.department_ids.includes(d.id)"
@update:modelValue="checked => toggleDept(d.id, checked)"
/>
<span>{{ d.name }}</span>
</div>
</div>
<p v-if="form.errors.department_ids" class="mt-1 text-sm text-red-600">
{{ form.errors.department_ids }}
</p>
</div>
<!-- Actions -->
<div class="flex items-center justify-end space-x-4">
<Link :href="route('smartdok.projects.index')">
<Button variant="outline">Avbryt</Button>
</Link>
<Button type="submit" :disabled="form.processing">
Oppdater
</Button>
</div>
</form>
</div>
</div>
</AppLayout>
</template>
+126
View File
@@ -0,0 +1,126 @@
<script setup lang="ts">
import { computed } from 'vue';
import AppLayout from '@/layouts/AppLayout.vue';
import { type BreadcrumbItem } from '@/types';
import { Head, Link, usePage } from '@inertiajs/vue3';
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Plus } from 'lucide-vue-next';
import ProjectDropdown from './ProjectDropdown.vue';
interface Project {
id: number
project_name: string
project_number: string
};
const page = usePage<{
projects: {
data: Project[]
current_page: number
last_page: number
per_page: number
total: number
}
}>();
// Reactive list of projects
const projects = computed(() => page.props.projects.data);
// Breadcrumbs
const breadcrumbs: BreadcrumbItem[] = [
{ title: 'Dashbord', href: '/dashboard' },
{ title: 'Prosjekter', href: '/smartdok/projects' },
];
// Pagination meta
const currentPage = computed(() => page.props.projects.current_page);
const lastPage = computed(() => page.props.projects.last_page);
const prevPage = computed(() =>
currentPage.value > 1 ? currentPage.value - 1 : 1
);
const nextPage = computed(() =>
currentPage.value < lastPage.value ? currentPage.value + 1 : lastPage.value
);
</script>
<template>
<Head title="Prosjekter" />
<AppLayout :breadcrumbs="breadcrumbs">
<!-- Header + Create-button -->
<div class="mb-6 flex items-center justify-between p-6">
<h3 class="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-3xl">
Prosjekter
</h3>
<Link :href="route('smartdok.projects.create')">
<Button variant="outline">
<Plus class="mr-2 h-4 w-4" />
Opprett
</Button>
</Link>
</div>
<!-- Table -->
<div class="px-6 overflow-auto">
<Table>
<TableCaption>En liste over prosjekter.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Prosjektnavn</TableHead>
<TableHead>Prosjektnummer</TableHead>
<TableHead class="text-right">Handlinger</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow
v-for="project in projects"
:key="project.id"
>
<TableCell class="font-medium">
{{ project.project_name }}
</TableCell>
<TableCell>
{{ project.project_number }}
</TableCell>
<TableCell class="text-right">
<ProjectDropdown :project="project" />
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<!-- Pagination controls -->
<div class="flex items-center justify-between p-6">
<div class="text-sm text-gray-600">
Side {{ currentPage }} av {{ lastPage }} &middot;
Totalt {{ page.props.projects.total }} prosjekter
</div>
<div class="space-x-2">
<Link
:href="route('smartdok.projects.index', { page: prevPage })"
>
<Button variant="outline" :disabled="currentPage === 1">
Forrige
</Button>
</Link>
<Link
:href="route('smartdok.projects.index', { page: nextPage })"
>
<Button variant="outline" :disabled="currentPage === lastPage">
Neste
</Button>
</Link>
</div>
</div>
</AppLayout>
</template>
@@ -0,0 +1,96 @@
<script setup lang="ts">
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { MoreHorizontal } from 'lucide-vue-next';
import {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogCancel,
AlertDialogAction,
} from '@/components/ui/alert-dialog';
import { Link } from '@inertiajs/vue3';
defineProps<{
project: {
id: number
}
}>();
function copy(id: number) {
navigator.clipboard.writeText(id.toString());
}
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="ghost" class="w-8 h-8 p-0">
<span class="sr-only">Åpne meny</span>
<MoreHorizontal class="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Handlinger</DropdownMenuLabel>
<DropdownMenuItem @click="copy(project.id)">
Kopier ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem as-child>
<Link :href="route('smartdok.projects.show', project.id)">
Vis prosjekt
</Link>
</DropdownMenuItem>
<DropdownMenuItem as-child>
<Link :href="route('smartdok.projects.edit', project.id)">
Rediger
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<AlertDialog>
<AlertDialogTrigger asChild>
<DropdownMenuItem
className="text-red-600 rounded-sm hover:!bg-zinc-800 cursor-pointer flex items-center py-1 px-2"
:onSelect="(e) => e.preventDefault()"
>Slett</DropdownMenuItem>
</AlertDialogTrigger>
<!-- Confirmation dialog -->
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Er du sikker på at du vil slette dette prosjektet?</AlertDialogTitle>
<AlertDialogDescription>
Denne handlingen kan ikke angres.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Avbryt</AlertDialogCancel>
<AlertDialogAction as-child>
<Link
method="delete"
:href="route('smartdok.projects.destroy', project.id)"
class="text-red-600"
as="button"
>
Slett
</Link>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</DropdownMenuContent>
</DropdownMenu>
</template>
+103
View File
@@ -0,0 +1,103 @@
<script setup lang="ts">
import { computed } from 'vue';
import AppLayout from '@/layouts/AppLayout.vue';
import { type BreadcrumbItem } from '@/types';
import { Head, Link, usePage } from '@inertiajs/vue3';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
interface Profile {
id: string
username: string
name: string
};
interface Department {
id: number
name: string
};
interface Project {
id: number
project_name: string
project_number: string
profiles: Profile[]
departments: Department[]
};
const page = usePage<{ project: Project }>();
const project = computed(() => page.props.project);
const breadcrumbs = computed<BreadcrumbItem[]>(() => [
{ title: 'Dashbord', href: '/dashboard' },
{ title: 'Prosjekter', href: '/smartdok/projects' },
{
title: project.value.project_name,
href: route('smartdok.projects.show', project.value.id),
},
]);
</script>
<template>
<Head title="Vis prosjekt" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="p-6 space-y-8">
<!-- Heading + actions -->
<div class="flex items-center justify-between">
<h3 class="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-3xl">
Vis prosjekt
</h3>
<div class="flex space-x-2">
<Link :href="route('smartdok.projects.index')">
<Button variant="outline">Tilbake</Button>
</Link>
<Link :href="route('smartdok.projects.edit', project.id)">
<Button>Rediger</Button>
</Link>
</div>
</div>
<!-- Project details -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<Label for="id" className="font-semibold">ID</Label>
<p id="id" class="mt-1 text-base">
{{ project.id }}
</p>
</div>
<div>
<Label for="project_name" className="font-semibold">Prosjektnavn</Label>
<p id="project_name" class="mt-1 text-base">
{{ project.project_name }}
</p>
</div>
<div>
<Label for="project_number" className="font-semibold">Prosjektnummer</Label>
<p id="project_number" class="mt-1 text-base">
{{ project.project_number }}
</p>
</div>
</div>
<!-- Tilknyttede brukere -->
<div>
<Label className="font-semibold">Brukere</Label>
<ul class="list-disc list-inside mt-2">
<li v-for="p in project.profiles" :key="p.id">
{{ p.name }} ({{ p.username }})
</li>
</ul>
</div>
<!-- Tilknyttede avdelinger -->
<div>
<Label className="font-semibold">Avdelinger</Label>
<ul class="list-disc list-inside mt-2">
<li v-for="d in project.departments" :key="d.id">
{{ d.name }}
</li>
</ul>
</div>
</div>
</AppLayout>
</template>