dodalem pinia store.

dodalem dokumentacje opisujaca dzialanie store
dodalem services do komunikacji z directus
dodalem opis dzialania services
dodalem mocki danych
przygotowania do refaktoryzacji
This commit is contained in:
2026-04-30 16:35:53 +02:00
parent 5725c024dc
commit 6e3d722e69
19 changed files with 3286 additions and 29 deletions

View File

@@ -0,0 +1,73 @@
import { computed, ref } from 'vue'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { fetchDictionaries as fetchDictionariesFromApi } from 'src/services/dictionariesApi'
export const useDictionariesStore = defineStore('dictionaries', () => {
const models = ref([])
const clients = ref([])
const finishes = ref([])
const productionLists = ref([])
const operations = ref([])
const colors = ref([])
const loadedAt = ref(null)
const isLoading = ref(false)
const error = ref(null)
const isLoaded = computed(() => loadedAt.value !== null)
async function fetchDictionaries({ force = false } = {}) {
if (isLoading.value || (isLoaded.value && !force)) {
return
}
isLoading.value = true
error.value = null
try {
const data = await fetchDictionariesFromApi()
models.value = data.models
clients.value = data.clients
finishes.value = data.finishes
productionLists.value = data.productionLists
operations.value = data.operations
colors.value = data.colors
loadedAt.value = Date.now()
} catch (err) {
error.value = err
throw err
} finally {
isLoading.value = false
}
}
function clear() {
models.value = []
clients.value = []
finishes.value = []
productionLists.value = []
operations.value = []
colors.value = []
loadedAt.value = null
error.value = null
}
return {
models,
clients,
finishes,
productionLists,
operations,
colors,
loadedAt,
isLoading,
error,
isLoaded,
fetchDictionaries,
clear,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useDictionariesStore, import.meta.hot))
}

View File

@@ -0,0 +1,141 @@
import { computed, ref } from 'vue'
import { acceptHMRUpdate, defineStore } from 'pinia'
import {
fetchProductSpecification,
refreshProductSpecification,
} from 'src/services/productSpecificationApi'
function createEmptySpecification(productId) {
return {
productId,
orderId: null,
sourceUrl: null,
lastFetchedAt: null,
sections: [],
diff: [],
loadedAt: null,
isLoading: false,
isRefreshing: false,
error: null,
}
}
export const useProductSpecificationStore = defineStore('productSpecification', () => {
const byProductId = ref({})
const loadedProductIds = computed(() => Object.keys(byProductId.value).map(Number))
function ensureSpecification(productId) {
if (!byProductId.value[productId]) {
byProductId.value = {
...byProductId.value,
[productId]: createEmptySpecification(productId),
}
}
return byProductId.value[productId]
}
function setSpecification(productId, patch) {
const current = ensureSpecification(productId)
byProductId.value = {
...byProductId.value,
[productId]: {
...current,
...patch,
},
}
}
function getSpecification(productId) {
return byProductId.value[productId] ?? null
}
async function fetchSpecification(productId, { force = false } = {}) {
const current = ensureSpecification(productId)
if (current.isLoading || (current.loadedAt && !force)) {
return current
}
setSpecification(productId, {
isLoading: true,
error: null,
})
try {
const specification = await fetchProductSpecification(productId)
setSpecification(productId, {
productId,
orderId: specification.orderId,
sourceUrl: specification.sourceUrl,
lastFetchedAt: specification.lastFetchedAt,
sections: specification.sections,
diff: specification.diff,
loadedAt: Date.now(),
isLoading: false,
})
return byProductId.value[productId]
} catch (err) {
setSpecification(productId, {
isLoading: false,
error: err,
})
throw err
}
}
async function refreshSpecification(productId) {
ensureSpecification(productId)
setSpecification(productId, {
isRefreshing: true,
error: null,
})
try {
const specification = await refreshProductSpecification(productId)
setSpecification(productId, {
productId,
orderId: specification.orderId,
sourceUrl: specification.sourceUrl,
lastFetchedAt: specification.lastFetchedAt,
sections: specification.sections,
diff: specification.diff,
loadedAt: Date.now(),
isRefreshing: false,
})
return byProductId.value[productId]
} catch (err) {
setSpecification(productId, {
isRefreshing: false,
error: err,
})
throw err
}
}
function clearProduct(productId) {
const next = { ...byProductId.value }
delete next[productId]
byProductId.value = next
}
return {
byProductId,
loadedProductIds,
ensureSpecification,
getSpecification,
fetchSpecification,
refreshSpecification,
clearProduct,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useProductSpecificationStore, import.meta.hot))
}

View File

@@ -0,0 +1,126 @@
import { computed, ref } from 'vue'
import { acceptHMRUpdate, defineStore } from 'pinia'
import {
createProductTimelineEvent,
fetchProductTimeline,
} from 'src/services/productTimelineApi'
import { useProductsStore } from 'src/stores/productsStore'
function createEmptyTimeline(productId) {
return {
productId,
events: [],
loadedAt: null,
isLoading: false,
error: null,
}
}
export const useProductTimelineStore = defineStore('productTimeline', () => {
const byProductId = ref({})
const loadedProductIds = computed(() => Object.keys(byProductId.value).map(Number))
function ensureTimeline(productId) {
if (!byProductId.value[productId]) {
byProductId.value = {
...byProductId.value,
[productId]: createEmptyTimeline(productId),
}
}
return byProductId.value[productId]
}
function setTimeline(productId, patch) {
const current = ensureTimeline(productId)
byProductId.value = {
...byProductId.value,
[productId]: {
...current,
...patch,
},
}
}
function getEvents(productId) {
return byProductId.value[productId]?.events ?? []
}
async function fetchTimeline(productId, { force = false } = {}) {
const current = ensureTimeline(productId)
if (current.isLoading || (current.loadedAt && !force)) {
return current
}
setTimeline(productId, {
isLoading: true,
error: null,
})
try {
const timeline = await fetchProductTimeline(productId)
setTimeline(productId, {
events: timeline.events ?? [],
timelinePreview: timeline.timelinePreview,
loadedAt: Date.now(),
isLoading: false,
})
if (timeline.timelinePreview) {
useProductsStore().applyTimelinePreviewUpdate(productId, timeline.timelinePreview)
}
return byProductId.value[productId]
} catch (err) {
setTimeline(productId, {
isLoading: false,
error: err,
})
throw err
}
}
async function addEvent(productId, payload) {
const { event: createdEvent, timelinePreview } = await createProductTimelineEvent(
productId,
payload,
)
const current = ensureTimeline(productId)
setTimeline(productId, {
events: [...current.events, createdEvent],
loadedAt: Date.now(),
})
if (timelinePreview) {
useProductsStore().applyTimelinePreviewUpdate(productId, timelinePreview)
}
return createdEvent
}
function clearProduct(productId) {
const next = { ...byProductId.value }
delete next[productId]
byProductId.value = next
}
return {
byProductId,
loadedProductIds,
ensureTimeline,
getEvents,
fetchTimeline,
addEvent,
clearProduct,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useProductTimelineStore, import.meta.hot))
}

View File

@@ -0,0 +1,156 @@
import { computed, ref } from 'vue'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { fetchProducts } from 'src/services/productsApi'
const DEFAULT_LIMIT = 30
function createDefaultFilters() {
return {
search: '',
modelId: null,
clientId: null,
finish: null,
productionListId: null,
year: null,
}
}
export const useProductsStore = defineStore('products', () => {
const ids = ref([])
const byId = ref({})
const filters = ref(createDefaultFilters())
const limit = ref(DEFAULT_LIMIT)
const offset = ref(0)
const total = ref(null)
const hasMore = ref(true)
const isLoading = ref(false)
const error = ref(null)
const items = computed(() => ids.value.map((id) => byId.value[id]).filter(Boolean))
const count = computed(() => ids.value.length)
function buildListParams() {
return {
limit: limit.value,
offset: offset.value,
search: filters.value.search || undefined,
model: filters.value.modelId || undefined,
client: filters.value.clientId || undefined,
finish: filters.value.finish || undefined,
productionList: filters.value.productionListId || undefined,
year: filters.value.year || undefined,
}
}
function setProducts(products, { append = false } = {}) {
const nextIds = append ? [...ids.value] : []
const nextById = append ? { ...byId.value } : {}
for (const product of products) {
nextById[product.id] = product
if (!nextIds.includes(product.id)) {
nextIds.push(product.id)
}
}
ids.value = nextIds
byId.value = nextById
}
async function fetchFirstPage() {
offset.value = 0
hasMore.value = true
return fetchNextPage({ append: false })
}
async function fetchNextPage({ append = true } = {}) {
if (isLoading.value || !hasMore.value) {
return
}
isLoading.value = true
error.value = null
try {
const { items: products, pageInfo } = await fetchProducts(buildListParams())
setProducts(products, { append })
offset.value = append ? offset.value + products.length : products.length
total.value = pageInfo.total ?? total.value
hasMore.value = pageInfo.hasMore ?? products.length === limit.value
} catch (err) {
error.value = err
throw err
} finally {
isLoading.value = false
}
}
function setFilters(nextFilters) {
filters.value = {
...filters.value,
...nextFilters,
}
}
async function applyFilters(nextFilters) {
setFilters(nextFilters)
await fetchFirstPage()
}
function updateProduct(productId, patch) {
const current = byId.value[productId]
if (!current) {
return
}
byId.value = {
...byId.value,
[productId]: {
...current,
...patch,
},
}
}
function applyTimelinePreviewUpdate(productId, timelinePreview) {
updateProduct(productId, { timelinePreview })
}
function clear() {
ids.value = []
byId.value = {}
offset.value = 0
total.value = null
hasMore.value = true
error.value = null
}
return {
ids,
byId,
filters,
limit,
offset,
total,
hasMore,
isLoading,
error,
items,
count,
fetchFirstPage,
fetchNextPage,
setFilters,
applyFilters,
updateProduct,
applyTimelinePreviewUpdate,
clear,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useProductsStore, import.meta.hot))
}

View File

@@ -0,0 +1,54 @@
import { computed, ref } from 'vue'
import { acceptHMRUpdate, defineStore } from 'pinia'
export const UI_PANELS = {
ADVANCED_SEARCH: 'advancedSearch',
PRODUCT_SPECIFICATION: 'productSpecification',
PRODUCTION_STATUSES: 'productionStatuses',
PRODUCT_TIMELINE: 'productTimeline',
}
export const useUiStore = defineStore('ui', () => {
const isDrawerOpen = ref(false)
const activePanel = ref(null)
const activeProductId = ref(null)
const drawerPayload = ref({})
const drawerInstanceKey = ref(0)
const hasActivePanel = computed(() => activePanel.value !== null)
function openDrawer(panel, payload = {}) {
isDrawerOpen.value = true
activePanel.value = panel
activeProductId.value = payload.productId ?? null
drawerPayload.value = payload
drawerInstanceKey.value += 1
}
function replaceDrawer(panel, payload = {}) {
openDrawer(panel, payload)
}
function closeDrawer() {
isDrawerOpen.value = false
activePanel.value = null
activeProductId.value = null
drawerPayload.value = {}
}
return {
isDrawerOpen,
activePanel,
activeProductId,
drawerPayload,
drawerInstanceKey,
hasActivePanel,
openDrawer,
replaceDrawer,
closeDrawer,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUiStore, import.meta.hot))
}