Merge branch 'test' of ssh://git.ma-al.com:8822/goc_daniel/b2b into filters

This commit is contained in:
2026-03-25 02:35:25 +01:00
5 changed files with 430 additions and 387 deletions

View File

@@ -48,17 +48,13 @@
"name": "Locale",
"description": "Locale selection endpoints (under /api/v1/restricted/langs-and-countries, requires authentication)"
},
{
"name": "Repo",
"description": "Repository time tracking data endpoints (under /api/v1/restricted/repo, requires authentication)"
},
{
"name": "Admin",
"description": "Admin-only endpoints"
},
{
"name": "Settings",
"description": "Application settings and configuration endpoints"
},
{
"name": "Carts",
"description": "Shopping cart management endpoints (under /api/v1/restricted/carts, requires authentication)"
}
],
"paths": {
@@ -629,6 +625,41 @@
}
}
},
"/api/v1/public/auth/update-choice": {
"post": {
"tags": ["Auth"],
"summary": "Update JWT token choice",
"description": "Updates the user's JWT token preference or refreshes the token. Requires authentication.",
"operationId": "updateChoice",
"security": [
{
"CookieAuth": []
}
],
"responses": {
"200": {
"description": "Token choice updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/public/auth/google": {
"get": {
"tags": ["Auth"],
@@ -733,292 +764,6 @@
}
}
},
"/api/v1/restricted/repo/get-repos": {
"get": {
"tags": ["Repo"],
"summary": "Get accessible repositories",
"description": "Returns a list of repository IDs that the authenticated user has access to.",
"operationId": "getRepos",
"security": [
{
"CookieAuth": []
}
],
"responses": {
"200": {
"description": "List of repository IDs",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "integer",
"format": "uint"
},
"example": [1, 2, 5]
}
}
}
},
"400": {
"description": "Invalid user session",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/restricted/repo/get-years": {
"get": {
"tags": ["Repo"],
"summary": "Get available years for a repository",
"description": "Returns a list of years for which tracked time data exists in the given repository. User must have access to the repository.",
"operationId": "getYears",
"security": [
{
"CookieAuth": []
}
],
"parameters": [
{
"name": "repoID",
"in": "query",
"description": "Repository ID",
"required": true,
"schema": {
"type": "integer",
"format": "uint"
}
}
],
"responses": {
"200": {
"description": "List of years with tracked time data",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "integer",
"format": "uint"
},
"example": [2023, 2024, 2025]
}
}
}
},
"400": {
"description": "Invalid repoID parameter or user does not have access to the repository",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/restricted/repo/get-quarters": {
"get": {
"tags": ["Repo"],
"summary": "Get quarterly time data for a repository",
"description": "Returns time tracked per quarter for the given repository and year. All 4 quarters are returned; quarters with no data have time=0. User must have access to the repository.",
"operationId": "getQuarters",
"security": [
{
"CookieAuth": []
}
],
"parameters": [
{
"name": "repoID",
"in": "query",
"description": "Repository ID",
"required": true,
"schema": {
"type": "integer",
"format": "uint"
}
},
{
"name": "year",
"in": "query",
"description": "Year to retrieve quarterly data for",
"required": true,
"schema": {
"type": "integer",
"format": "uint",
"example": 2024
}
}
],
"responses": {
"200": {
"description": "Quarterly time data",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/QuarterData"
}
}
}
}
},
"400": {
"description": "Invalid repoID or year parameter, or user does not have access to the repository",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/restricted/repo/get-issues": {
"get": {
"tags": ["Repo"],
"summary": "Get issues with time summaries",
"description": "Returns a paginated list of issues with time tracking summaries for the given repository, year, and quarter. User must have access to the repository.",
"operationId": "getIssues",
"security": [
{
"CookieAuth": []
}
],
"parameters": [
{
"name": "repoID",
"in": "query",
"description": "Repository ID",
"required": true,
"schema": {
"type": "integer",
"format": "uint"
}
},
{
"name": "year",
"in": "query",
"description": "Year to filter issues by",
"required": true,
"schema": {
"type": "integer",
"format": "uint",
"example": 2024
}
},
{
"name": "quarter",
"in": "query",
"description": "Quarter number (1-4) to filter issues by",
"required": true,
"schema": {
"type": "integer",
"format": "uint",
"minimum": 1,
"maximum": 4,
"example": 2
}
},
{
"name": "page_number",
"in": "query",
"description": "Page number for pagination (1-based)",
"required": true,
"schema": {
"type": "integer",
"format": "uint",
"example": 1
}
},
{
"name": "elements_per_page",
"in": "query",
"description": "Number of items per page",
"required": true,
"schema": {
"type": "integer",
"format": "uint",
"example": 30
}
}
],
"responses": {
"200": {
"description": "Paginated list of issues with time summaries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PaginatedIssues"
}
}
}
},
"400": {
"description": "Invalid parameters or user does not have access to the repository",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/settings": {
"get": {
"tags": ["Settings"],
@@ -1043,7 +788,7 @@
"get": {
"tags": ["Products"],
"summary": "Get product listing",
"description": "Returns a paginated list of products with their basic information. Supports filtering via query parameters with operators (e.g., product_id_eq=12, name=~gold). Use sort parameter for ordering. Requires authentication.",
"description": "Returns a paginated list of products with their basic information. Supports filtering via query parameters with operators (e.g., product_id_eq=12, name=~wałek). Use sort parameter for ordering. Requires authentication.",
"operationId": "getProductListing",
"security": [
{
@@ -1093,11 +838,10 @@
{
"name": "name",
"in": "query",
"description": "Filter by product name using LIKE (case-insensitive). Use ~ prefix for partial match (e.g., '~gold')",
"description": "Filter by product name using LIKE (case-insensitive). Use ~ prefix for partial match (e.g., '~wałek')",
"required": false,
"schema": {
"type": "string",
"example": "~gold"
"type": "string"
}
},
{
@@ -1767,6 +1511,280 @@
}
}
}
},
"/api/v1/restricted/carts/add-new-cart": {
"get": {
"tags": ["Carts"],
"summary": "Create a new cart",
"description": "Creates a new shopping cart for the authenticated user. Requires authentication.",
"operationId": "addNewCart",
"security": [
{
"CookieAuth": []
}
],
"responses": {
"200": {
"description": "Cart created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/restricted/carts/change-cart-name": {
"get": {
"tags": ["Carts"],
"summary": "Change cart name",
"description": "Updates the name of an existing cart. Requires authentication.",
"operationId": "changeCartName",
"security": [
{
"CookieAuth": []
}
],
"parameters": [
{
"name": "cart_id",
"in": "query",
"description": "ID of the cart to rename",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "new_name",
"in": "query",
"description": "New name for the cart",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Cart name updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"400": {
"description": "Invalid request parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/restricted/carts/retrieve-carts-info": {
"get": {
"tags": ["Carts"],
"summary": "Retrieve all carts info",
"description": "Returns information about all carts belonging to the authenticated user. Requires authentication.",
"operationId": "retrieveCartsInfo",
"security": [
{
"CookieAuth": []
}
],
"responses": {
"200": {
"description": "Carts info retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/restricted/carts/retrieve-cart": {
"get": {
"tags": ["Carts"],
"summary": "Retrieve cart details",
"description": "Returns detailed contents of a specific cart. Requires authentication.",
"operationId": "retrieveCart",
"security": [
{
"CookieAuth": []
}
],
"parameters": [
{
"name": "cart_id",
"in": "query",
"description": "ID of the cart to retrieve",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Cart retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"400": {
"description": "Invalid request parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/api/v1/restricted/carts/add-product-to-cart": {
"get": {
"tags": ["Carts"],
"summary": "Add product to cart",
"description": "Adds a product to the specified cart. Requires authentication.",
"operationId": "addProductToCart",
"security": [
{
"CookieAuth": []
}
],
"parameters": [
{
"name": "cart_id",
"in": "query",
"description": "ID of the cart to add product to",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "product_id",
"in": "query",
"description": "ID of the product to add",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "product_attribute_id",
"in": "query",
"description": "ID of the product attribute (optional, for product variants)",
"required": false,
"schema": {
"type": "integer"
}
},
{
"name": "amount",
"in": "query",
"description": "Quantity to add",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Product added to cart successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"400": {
"description": "Invalid request parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"components": {

View File

@@ -69,7 +69,7 @@ type ProductInList struct {
CategoryName string `gorm:"column:category_name" json:"category_name" form:"category_name"`
Reference string `gorm:"column:reference" json:"reference"`
VariantsNumber uint `gorm:"column:variants_number" json:"variants_number"`
Quantity uint `gorm:"column:quantity" json:"quantity"`
Quantity int64 `gorm:"column:quantity" json:"quantity"`
}
type ProductFilters struct {

View File

@@ -7,7 +7,8 @@
<div v-if="!customerStore.hasAccount" class="flex flex-col items-center justify-center py-12">
<div class="text-center flex flex-col items-center justify-center mb-6">
<UIcon name="mdi:domain" class="text-[60px] text-gray-400 dark:text-gray-500" />
<p class="mt-4 text-lg text-gray-600 dark:text-gray-400">{{ t('No customer account found') }}</p>
<p class="mt-4 text-lg text-gray-600 dark:text-gray-400">{{ t('No customer account found') }}
</p>
<p class="text-sm text-gray-500 dark:text-gray-500">{{ t('Create an account to manage your company data') }}</p>
</div>
<UButton color="primary" @click="goToCreateAccount"
@@ -26,46 +27,56 @@
class="text-[24px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
{{ t('Company Information') }}
</h2>
<div class="grid grid-cols-1 gap-10">
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
<div>
<label
class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{ t('Company Name') }}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.companyName || '-' }}
<p class="text-black dark:text-white">{{ customerStore.customer?.companyName || '-'}}
</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{t('Company Email') }}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.companyEmail || '-' }}
<label
class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{
t('Company Email') }}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.companyEmail || '-'}}
</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{t('REGON') }}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.regon || '-' }}</p>
<label
class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{ t('REGON')}}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.regon || '-' }}
</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{t('NIP') }}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.nip || '-' }}</p>
<label
class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{t('NIP')}}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.nip || '-' }}
</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{t('VAT') }}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.vat || '-' }}</p>
<label
class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{t('VAT')}}</label>
<p class="text-black dark:text-white">{{ customerStore.customer?.vat || '-' }}
</p>
</div>
</div>
</div>
<div class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
<h2 class="text-xl font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
<div>
<h2
class="text-xl font-semibold text-black dark:text-white mb-2 flex items-center gap-2">
<UIcon name="mdi:map-marker"
class="text-[24px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
{{ t('Addresses') }}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">{{ t('Company Address') }}</label>
<label
class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">{{ t('Company Address') }}</label>
<div v-if="companyAddress"
class="p-4 bg-white dark:bg-(--black) rounded-md border border-(--border-light) dark:border-(--border-dark)">
<p class="text-black dark:text-white">{{ companyAddress.street }}</p>
<p class="text-black dark:text-white">{{ companyAddress.zipCode }}, {{
companyAddress.city }}</p>
<p class="text-black dark:text-white">{{ companyAddress.zipCode }},
{{ companyAddress.city }}</p>
<p class="text-black dark:text-white">{{ companyAddress.country }}</p>
</div>
<p v-else class="text-gray-400 dark:text-gray-500">-</p>
@@ -73,6 +84,8 @@
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-end">
<UButton color="primary" variant="outline"
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) border-(--accent-blue-light) dark:border-(--accent-blue-dark)"

View File

@@ -35,7 +35,7 @@
<tr v-for="product in productsList" :key="product.product_id"
class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-6 py-4 whitespace-nowrap">
<img :src="getImageUrl(product.ImageID, product.LinkRewrite,)" alt="product image"
<img :src="product.ImageID" alt="product image"
class="w-16 h-16 object-cover rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">{{
@@ -49,6 +49,9 @@
</tr>
</tbody>
</table>
<div class="flex justify-center items-center py-8">
<UPagination v-model:page="page" :total="total" :page-size="perPage" />
</div>
<div v-if="productsList.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
No products found
</div>
@@ -59,17 +62,21 @@
</template>
<script setup lang="ts">
import { ref, onMounted, Suspense } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { useFetchJson } from '@/composable/useFetchJson'
import Default from '@/layouts/default.vue'
// import CategoryMenu from '@/components/inner/categoryMenu.vue'
interface Product {
product_id: number
name: string
ImageID: number
ImageID: string
LinkRewrite: string
}
const page = ref(1)
const perPage = ref(15)
const total = ref(0)
interface ApiResponse {
message: string
items: Product[]
@@ -80,21 +87,26 @@ const productsList = ref<Product[]>([])
const loading = ref(true)
const error = ref<string | null>(null)
function getImageUrl(imageID: number, linkRewrite: string, size: string = 'small_default') {
return `https://www.naluconcept.com/${imageID}-${size}/${linkRewrite}.webp`
}
async function fetchProductList() {
loading.value = true
error.value = null
try {
const response = await useFetchJson('/api/v1/restricted/list-products/get-listing?p&elems&shopID=1') as ApiResponse
const response = await useFetchJson(
`api/v1/restricted/list-products/get-listing?p=${page.value}&elems=${perPage.value}`
) as ApiResponse
productsList.value = response.items || []
total.value = response.count || 0
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Failed to load products'
console.error(e)
} finally {
loading.value = false
}
}
watch(page, () => {
fetchProductList()
})
onMounted(fetchProductList)
</script>

View File

@@ -238,7 +238,7 @@ const columns = computed<TableColumn<IssueTimeSummary>[]>(() => [
</h2>
<UTable :data="issues" :columns="columns" class="flex-1 dark:text-white! text-dark" />
<div class="pt-4 flex justify-center items-center dark:text-white! text-dark">
<UPagination v-model:page="page" :total="totalItems" />
<UPagination v-model:page="page" :total="totalItems" :page-size="10" />
</div>
</div>