Initial commit: PC Builder AdonisJS project
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from 'vue'
|
||||
import { Link, usePage } from '@inertiajs/vue3'
|
||||
|
||||
const { props } = usePage<{
|
||||
components: any[]
|
||||
states: any[]
|
||||
errors: any[]
|
||||
}>()
|
||||
const { components, states } = props
|
||||
const errors = computed(() => props.errors || [])
|
||||
|
||||
const componentTypeOrder = [
|
||||
'Processor',
|
||||
'Motherboard',
|
||||
'Graphic Card',
|
||||
'Memory',
|
||||
'SSD',
|
||||
'Storage',
|
||||
'Cooler',
|
||||
'Power Supply',
|
||||
'Case',
|
||||
'WiFi-Card',
|
||||
'Fan',
|
||||
'Other',
|
||||
]
|
||||
|
||||
const componentsByType = computed(() => {
|
||||
const groups = new Map<string, any[]>()
|
||||
components.forEach((component) => {
|
||||
if (!groups.has(component.type.name)) {
|
||||
groups.set(component.type.name, [])
|
||||
}
|
||||
groups.get(component.type.name)!.push(component)
|
||||
})
|
||||
return Array.from(groups)
|
||||
.map(([type, components]) => ({ type, components }))
|
||||
.sort((a, b) => componentTypeOrder.indexOf(a.type) - componentTypeOrder.indexOf(b.type))
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
title: '',
|
||||
sellPrice: 0,
|
||||
gpuScore: 0,
|
||||
cpuScore: 0,
|
||||
stateId: 1,
|
||||
globalScore: 0,
|
||||
componentsId: [] as number[],
|
||||
})
|
||||
|
||||
function getErrorMessage(field: string): string | null {
|
||||
const error = errors.value.find((error) => error.field === field)
|
||||
return error ? error.message : null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-4xl mx-auto p-8 bg-white shadow-lg rounded-lg">
|
||||
<h1 class="text-2xl font-semibold mb-8 text-gray-800">Create New Computer</h1>
|
||||
<form class="space-y-6">
|
||||
<div class="mt-2">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="title">Title</label>
|
||||
<input
|
||||
name="title"
|
||||
v-model="form.title"
|
||||
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-2 grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="gpuScore">GPU Score</label>
|
||||
<input
|
||||
name="gpuScore"
|
||||
v-model="form.gpuScore"
|
||||
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="cpuScore">CPU Score</label>
|
||||
<input
|
||||
name="cpuScore"
|
||||
v-model="form.cpuScore"
|
||||
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="globalScore"
|
||||
>Global Score</label
|
||||
>
|
||||
<input
|
||||
name="globalScore"
|
||||
v-model="form.globalScore"
|
||||
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="components"
|
||||
>Components</label
|
||||
>
|
||||
<div v-for="{ type, components } in componentsByType" :key="type">
|
||||
<label :for="`select-${type}`" class="block text-gray-700 text-sm font-bold mb-1 mt-2">
|
||||
{{ type }}
|
||||
</label>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
<div
|
||||
v-for="component in components"
|
||||
:key="component.id"
|
||||
class="flex items-center mt-1"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="`checkbox-${component.id}`"
|
||||
:value="component.id"
|
||||
v-model="form.componentsId"
|
||||
class="mr-2"
|
||||
/>
|
||||
<label :for="`checkbox-${component.id}`" class="text-sm text-gray-700">
|
||||
{{ component.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 grid grid-cols-3 gap-4">
|
||||
<div class="pt-2">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="components">State</label>
|
||||
<select
|
||||
v-model="form.stateId"
|
||||
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring-indigo-500 px-3 py-2.5 focus:ring-1 border rounded text-gray-700 bg-white"
|
||||
>
|
||||
<option disabled value="">Please select one</option>
|
||||
<option v-for="state in states" :key="state.id" :value="state.id">
|
||||
{{ state.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div />
|
||||
<div class="mt-2">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="sellPrice"
|
||||
>Sell Price</label
|
||||
>
|
||||
<input
|
||||
v-model="form.sellPrice"
|
||||
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="number"
|
||||
step="0.01"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/computers"
|
||||
type="button"
|
||||
as="button"
|
||||
method="post"
|
||||
:data="form"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition"
|
||||
>
|
||||
Create
|
||||
</Link>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from 'vue'
|
||||
import { Link, router, usePage } from '@inertiajs/vue3'
|
||||
|
||||
const { props } = usePage<{
|
||||
computer: any
|
||||
availableComponents: any[]
|
||||
states: any[]
|
||||
errors?: any[]
|
||||
}>()
|
||||
const { computer, availableComponents, states } = props
|
||||
const errors = computed(() => props.errors || [])
|
||||
|
||||
const form = reactive({
|
||||
id: computer.id,
|
||||
title: computer.title,
|
||||
sellPrice: computer.sellPrice,
|
||||
gpuScore: computer.gpuScore,
|
||||
cpuScore: computer.cpuScore,
|
||||
globalScore: computer.globalScore,
|
||||
stateId: computer.state.id,
|
||||
componentsId: computer.components.map((c: any) => c.id),
|
||||
})
|
||||
|
||||
function updateComponents(componentsId: number[]) {
|
||||
form.componentsId = componentsId
|
||||
}
|
||||
|
||||
function deleteComputer(id: number) {
|
||||
router.delete(`/computers/${id}`)
|
||||
}
|
||||
|
||||
function getErrorMessage(field: string): string | null {
|
||||
const error = errors.value.find((error) => error.field === field)
|
||||
return error ? error.message : null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-4xl mx-auto p-8 bg-white shadow-lg rounded-lg">
|
||||
<h1 class="text-2xl font-semibold mb-8 text-gray-800">Edit Computer</h1>
|
||||
<form class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="title">Title</label>
|
||||
<input
|
||||
v-model="form.title"
|
||||
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="sellPrice">Sell Price</label>
|
||||
<input
|
||||
name="sell_price"
|
||||
v-model="form.sellPrice"
|
||||
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="number"
|
||||
step="0.01"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="gpuScore">GPU Score</label>
|
||||
<input
|
||||
v-model="form.gpuScore"
|
||||
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="cpuScore">CPU Score</label>
|
||||
<input
|
||||
v-model="form.cpuScore"
|
||||
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="globalScore"
|
||||
>Global Score</label
|
||||
>
|
||||
<input
|
||||
v-model="form.globalScore"
|
||||
class="w-full border-gray-200 focus:border-indigo-600 focus:ring focus:ring-indigo-500/40 px-3 py-2 border rounded text-gray-700"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-2">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="components">State</label>
|
||||
<select
|
||||
v-model="form.stateId"
|
||||
class="w-full mt-2 border-gray-200 focus:border-indigo-600 focus:ring-indigo-500 px-3 py-2.5 focus:ring-1 border rounded text-gray-700 bg-white"
|
||||
>
|
||||
<option disabled value="">Please select one</option>
|
||||
<option v-for="state in states" :key="state.id" :value="state.id">
|
||||
{{ state.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col md:flex-row items-stretch justify-between space-y-2 md:space-y-0 md:space-x-2"
|
||||
>
|
||||
<Link
|
||||
:href="`/computers/${form.id}`"
|
||||
method="put"
|
||||
:data="form"
|
||||
as="button"
|
||||
class="w-full md:w-auto px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition flex justify-center"
|
||||
>
|
||||
Save
|
||||
</Link>
|
||||
<button
|
||||
@click="deleteComputer(computer.id)"
|
||||
class="w-full md:w-auto px-4 py-2 bg-gray-300 text-white rounded hover:bg-red-500 transition flex items-center justify-center"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,128 @@
|
||||
<script setup lang="ts">
|
||||
import { Link } from '@inertiajs/vue3'
|
||||
|
||||
defineProps<{ computers: any[] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-3xl font-semibold text-gray-700">Computers</h3>
|
||||
|
||||
<Link
|
||||
href="/computers/create"
|
||||
type="button"
|
||||
as="button"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition"
|
||||
>
|
||||
Add
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<template v-if="computers.length > 0">
|
||||
<div
|
||||
v-for="computer in computers"
|
||||
:key="computer.id"
|
||||
class="p-4 border rounded-lg shadow hover:shadow-lg transition"
|
||||
>
|
||||
<h4 class="font-semibold text-lg text-gray-800">{{ computer.title }}</h4>
|
||||
<p class="text-gray-600">State: {{ computer.state?.name }}</p>
|
||||
<p class="text-indigo-600 font-semibold">Price: ${{ computer.sellPrice }}</p>
|
||||
<p class="text-gray-500 text-sm">{{ computer.components.length }} components</p>
|
||||
<Link
|
||||
:href="`/computers/${computer.id}`"
|
||||
class="mt-2 inline-block text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
View Details
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="text-gray-500">No computers yet. Create one to get started!</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.computers-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.computers-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.computer-card {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-color: #f3f4f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 1rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-content h3 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.card-content p {
|
||||
color: #6b7280;
|
||||
margin: 0 0 1rem 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { Link } from '@inertiajs/vue3'
|
||||
|
||||
const { props } = defineProps<{ computer: any }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-4xl mx-auto p-4 md:p-8 bg-white shadow-lg rounded-lg">
|
||||
<h1 class="text-xl md:text-2xl font-semibold mb-4 md:mb-8 text-gray-800">
|
||||
{{ computer.title }}
|
||||
</h1>
|
||||
|
||||
<div class="mt-2">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">Components</label>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full border-collapse">
|
||||
<thead class="bg-gray-200">
|
||||
<tr>
|
||||
<th class="border px-4 py-2 text-left">Name</th>
|
||||
<th class="border px-4 py-2 text-left">Type</th>
|
||||
<th class="border px-4 py-2 text-right">Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="component in computer.components"
|
||||
:key="component.id"
|
||||
class="hover:bg-gray-50"
|
||||
>
|
||||
<td class="border px-4 py-2">{{ component.name }}</td>
|
||||
<td class="border px-4 py-2">{{ component.type?.name }}</td>
|
||||
<td class="border px-4 py-2 text-right">${{ component.price }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-4">
|
||||
<div class="flex-1 min-w-[100px]">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">GPU Score</label>
|
||||
<p class="w-full border-gray-200 px-3 py-2 border rounded text-gray-700">
|
||||
{{ computer.gpuScore }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-1 min-w-[100px]">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">CPU Score</label>
|
||||
<p class="w-full border-gray-200 px-3 py-2 border rounded text-gray-700">
|
||||
{{ computer.cpuScore }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-1 min-w-[100px]">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">Global Score</label>
|
||||
<p class="w-full border-gray-200 px-3 py-2 border rounded text-gray-700">
|
||||
{{ computer.globalScore }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="pt-2">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">State</label>
|
||||
<p class="w-full mt-2 border-gray-200 px-3 py-2.5 border rounded text-gray-700">
|
||||
{{ computer.state.name }}
|
||||
</p>
|
||||
</div>
|
||||
<div></div>
|
||||
<div class="mt-2">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">Sell Price</label>
|
||||
<p class="w-full mt-2 border-gray-200 px-3 py-2 border rounded text-gray-700">
|
||||
${{ computer.sellPrice.toFixed(2) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
:href="`/computers/${computer.id}/edit`"
|
||||
type="button"
|
||||
as="button"
|
||||
class="w-full md:w-auto px-4 py-2 bg-indigo-600 text-white rounded hover:bg-blue-700 transition"
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user