feat: Add shared components, refactor stores, improve data-entry workflow
Shared Components: - Add CompanySelector.vue and PeriodSelector.vue components - Add AppHeader.vue and SlideMenu.vue layout components - Add shared stores factories (companies.js, accountingPeriod.js) - Add shared routes factories (companies.py, calendar.py) - Add shared models (company.py, calendar.py) - Add shared layout styles (header.css, navigation.css) Data Entry App: - Update CLAUDE.md with prod/test server documentation - Improve nomenclature sync service with better error handling - Update receipts router and CRUD operations - Add company/period stores using shared factories - Update App.vue layout with shared components - Fix OCRUploadZone file handling Reports App: - Refactor stores to use shared factories - Update App.vue to use shared layout components Infrastructure: - Replace start-data-entry.sh with separate dev/test scripts - Add .claude/rules for authentication, backend patterns, etc. - Add implementation plan for OCR receipt improvements - Clean up old documentation files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
196
shared/frontend/stores/companies.js
Normal file
196
shared/frontend/stores/companies.js
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Shared Companies Store Factory
|
||||
*
|
||||
* Creates a Pinia store for company selection that can be used by any ROA2WEB application.
|
||||
* Each app passes its own apiService and auth store instances.
|
||||
*
|
||||
* Usage:
|
||||
* import { createCompaniesStore } from '@shared/frontend/stores/companies';
|
||||
* import { apiService } from '../services/api';
|
||||
* import { useAuthStore } from './auth';
|
||||
* export const useCompanyStore = createCompaniesStore(apiService, useAuthStore);
|
||||
*/
|
||||
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, computed, watch } from "vue";
|
||||
|
||||
/**
|
||||
* Factory function to create a companies store
|
||||
* @param {Object} apiService - Axios instance configured for the app's API
|
||||
* @param {Function} useAuthStore - Reference to the auth store function
|
||||
* @returns {Function} Pinia store definition
|
||||
*/
|
||||
export function createCompaniesStore(apiService, useAuthStore) {
|
||||
return defineStore("companies", () => {
|
||||
// State
|
||||
const companies = ref([]);
|
||||
const selectedCompany = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
// Initialize from localStorage - per user
|
||||
const initializeSelectedCompany = () => {
|
||||
const authStore = useAuthStore();
|
||||
const username = authStore.user?.username;
|
||||
|
||||
if (!username) {
|
||||
console.log("[Companies] No username available for initialization");
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = `selected_company_${username}`;
|
||||
const saved = localStorage.getItem(key);
|
||||
if (saved) {
|
||||
try {
|
||||
const company = JSON.parse(saved);
|
||||
console.log(`[Companies] Loaded saved company for ${username}:`, company.name);
|
||||
return company;
|
||||
} catch (e) {
|
||||
console.error("Failed to parse saved company", e);
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Watch for auth user changes to restore selected company
|
||||
const authStore = useAuthStore();
|
||||
watch(
|
||||
() => authStore.user,
|
||||
(newUser) => {
|
||||
if (newUser && newUser.username && !selectedCompany.value) {
|
||||
const restoredCompany = initializeSelectedCompany();
|
||||
if (restoredCompany) {
|
||||
selectedCompany.value = restoredCompany;
|
||||
console.log("[Companies] Restored selected company:", restoredCompany.name);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// Getters
|
||||
const companyList = computed(() => companies.value);
|
||||
const hasCompanies = computed(() => companies.value.length > 0);
|
||||
const selectedCompanyId = computed(() => selectedCompany.value?.id_firma || null);
|
||||
|
||||
const companyListFormatted = computed(() => {
|
||||
return companies.value.map((company) => ({
|
||||
...company,
|
||||
displayName: company.fiscal_code
|
||||
? `${company.name} (${company.fiscal_code})`
|
||||
: company.name,
|
||||
}));
|
||||
});
|
||||
|
||||
// Actions
|
||||
const loadCompanies = async () => {
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
console.log("[Companies] Loading companies...");
|
||||
const response = await apiService.get("/companies");
|
||||
companies.value = response.data.companies || [];
|
||||
console.log("[Companies] Loaded", companies.value.length, "companies");
|
||||
|
||||
// Validate saved company is still accessible
|
||||
if (selectedCompany.value) {
|
||||
const exists = companies.value.find(
|
||||
(c) => c.id_firma === selectedCompany.value.id_firma
|
||||
);
|
||||
if (!exists) {
|
||||
console.warn("[Companies] Saved company not accessible, clearing");
|
||||
clearSelectedCompany();
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.detail || "Failed to load companies";
|
||||
console.error("Failed to load companies:", err);
|
||||
return { success: false, error: error.value };
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const setSelectedCompany = (company) => {
|
||||
selectedCompany.value = company;
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const username = authStore.user?.username;
|
||||
|
||||
if (!username) {
|
||||
console.warn("[Companies] Cannot save - no username");
|
||||
return;
|
||||
}
|
||||
|
||||
const key = `selected_company_${username}`;
|
||||
if (company) {
|
||||
localStorage.setItem(key, JSON.stringify(company));
|
||||
console.log(`[Companies] Saved company for ${username}:`, company.name);
|
||||
} else {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
};
|
||||
|
||||
const clearSelectedCompany = () => {
|
||||
selectedCompany.value = null;
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const username = authStore.user?.username;
|
||||
|
||||
if (username) {
|
||||
const key = `selected_company_${username}`;
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
};
|
||||
|
||||
const getCompanyById = (id_firma) => {
|
||||
return companies.value.find(
|
||||
(company) => company.id_firma === parseInt(id_firma)
|
||||
);
|
||||
};
|
||||
|
||||
const clearError = () => {
|
||||
error.value = null;
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
companies.value = [];
|
||||
selectedCompany.value = null;
|
||||
isLoading.value = false;
|
||||
error.value = null;
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const username = authStore.user?.username;
|
||||
if (username) {
|
||||
const key = `selected_company_${username}`;
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
companies,
|
||||
selectedCompany,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
// Getters
|
||||
companyList,
|
||||
companyListFormatted,
|
||||
hasCompanies,
|
||||
selectedCompanyId,
|
||||
|
||||
// Actions
|
||||
loadCompanies,
|
||||
setSelectedCompany,
|
||||
clearSelectedCompany,
|
||||
getCompanyById,
|
||||
clearError,
|
||||
reset,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user