--- description: globs: alwaysApply: true --- # SME Centralized Reporting System - Coding Standards & Development Patterns ## OVERVIEW This document defines coding standards, development patterns, and implementation guidelines for the SME Centralized Reporting System React Native application. --- ## 1. CODING STANDARDS & CONVENTIONS ### File Naming Conventions - **Components**: PascalCase (`ZohoProjectsDashboardScreen.tsx`) - **Services**: camelCase (`zohoProjectsAPI.ts`) - **Store files**: camelCase (`zohoProjectsSlice.ts`) - **Types**: PascalCase (`ProfileTypes.ts`) - **Constants**: UPPER_SNAKE_CASE (`API_ENDPOINTS.ts`) - **Utilities**: camelCase (`dateUtils.ts`) - **Hooks**: camelCase with 'use' prefix (`useZohoProjectsData.ts`) ### Directory Naming Conventions - **Modules**: camelCase (`auth`, `zohoProjects`, `hr`, `profile`) - **Components**: camelCase (`widgets`, `forms`, `screens`) - **Services**: camelCase (`integrations`, `analytics`) ### Module Directory Rules - Place `screens`, `widgets`, and `forms` inside `components/` within each module. - Keep `components/` for UI-only code. Do not add `utils` or `constants` under modules. - Share cross-cutting helpers via `src/shared/utils` and global constants via `src/shared/constants`. ### Variable & Function Naming ```typescript // Variables: camelCase const projectList = []; const isLoading = false; const userPreferences = {}; // Functions: camelCase with descriptive verbs const fetchZohoProjectsData = () => {}; const calculateOpenTasks = () => {}; const handleSubmitForm = () => {}; // Constants: UPPER_SNAKE_CASE const API_BASE_URL = ''; const MAX_RETRY_ATTEMPTS = 3; const DEFAULT_PAGE_SIZE = 20; // Interfaces/Types: PascalCase interface ZohoProject { id: string; name: string; } type LoadingState = 'idle' | 'loading' | 'succeeded' | 'failed'; ``` --- ### Inline Commenting Guidelines - Always add clear, concise inline comments for non-obvious logic, complex conditions, side-effects, and business rules. - Prefer sentence-case, developer-oriented comments that explain "why" not just "what". - Document assumptions, edge cases, units, and non-trivial parameter/return semantics. - Keep comments up-to-date when changing code; remove stale comments immediately. - For components, briefly annotate major sections (effects, handlers, conditional rendering) and tricky UI logic. Example: ```typescript // Fetch projects on mount and when filters change to ensure fresh data useEffect(() => { // Guard: avoid duplicate fetch while loading if (loading) return; dispatch(fetchZohoProjects()); }, [dispatch, loading, filters]); // Compute SLA breach percentage (0-100). Includes only active tasks. const breachRate = useMemo(() => { if (totalTasks === 0) return 0; // Avoid divide-by-zero return Math.round((breachedTasks / totalTasks) * 100); }, [breachedTasks, totalTasks]); ``` --- ## 2. COMPONENT STRUCTURE TEMPLATES ### Screen Component Template ```typescript import React, { useEffect, useState } from 'react'; import { View, Text, ScrollView, RefreshControl, StyleSheet, } from 'react-native'; import { useSelector, useDispatch } from 'react-redux'; import Icon from 'react-native-vector-icons/MaterialIcons'; // Shared imports import { Container, LoadingSpinner, ErrorState } from '@/shared/components/ui'; import { COLORS, FONTS, SPACING } from '@/shared/styles/theme'; // Module imports import { selectZohoProjects, selectZohoProjectsLoading } from '../store/selectors'; import { fetchZohoProjects } from '../store/zohoProjectsSlice'; // Types import type { ZohoProjectsScreenProps } from '../types'; const ZohoProjectsDashboardScreen: React.FC = ({ navigation, route, }) => { // State const [refreshing, setRefreshing] = useState(false); // Redux const dispatch = useDispatch(); const data = useSelector(selectZohoProjects); const loading = useSelector(selectZohoProjectsLoading); // Effects useEffect(() => { dispatch(fetchZohoProjects()); }, [dispatch]); // Handlers const handleRefresh = async () => { setRefreshing(true); await dispatch(fetchZohoProjects()).unwrap(); setRefreshing(false); }; const handleNavigateToDetails = (id: string) => { navigation.navigate('ProjectDetails', { id }); }; // Loading state if (loading && !data.length) { return ; } // Error state if (error) { return dispatch(fetchZohoProjects())} />; } return ( } > Zoho Projects {/* Dashboard content */} {/* Widgets and components */} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: COLORS.background, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: SPACING.md, backgroundColor: COLORS.surface, }, title: { fontSize: 24, fontFamily: FONTS.bold, color: COLORS.text, }, content: { padding: SPACING.md, }, }); export default ZohoProjectsDashboardScreen; ``` ### Widget Component Template ```typescript import React from 'react'; import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; import Icon from 'react-native-vector-icons/MaterialIcons'; import { COLORS, FONTS, SPACING, SHADOWS } from '@/shared/styles/theme'; interface RevenueChartProps { data: RevenueData[]; period: 'month' | 'quarter' | 'year'; onPeriodChange: (period: 'month' | 'quarter' | 'year') => void; onPress?: () => void; } const RevenueChart: React.FC = ({ data, period, onPeriodChange, onPress, }) => { const totalRevenue = data.reduce((sum, item) => sum + item.amount, 0); return ( Revenue Analytics {['month', 'quarter', 'year'].map((p) => ( onPeriodChange(p as any)} > {p.charAt(0).toUpperCase() + p.slice(1)} ))} ${totalRevenue.toLocaleString()} Total Revenue {/* Chart component would go here */} Chart Component ); }; const styles = StyleSheet.create({ container: { backgroundColor: COLORS.surface, borderRadius: 12, padding: SPACING.md, ...SHADOWS.medium, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: SPACING.md, }, titleContainer: { flexDirection: 'row', alignItems: 'center', }, title: { fontSize: 16, fontFamily: FONTS.medium, color: COLORS.text, marginLeft: SPACING.xs, }, periodSelector: { flexDirection: 'row', backgroundColor: COLORS.background, borderRadius: 6, padding: 2, }, periodButton: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 4, }, periodButtonActive: { backgroundColor: COLORS.primary, }, periodText: { fontSize: 12, fontFamily: FONTS.regular, color: COLORS.textLight, }, periodTextActive: { color: COLORS.surface, }, content: { alignItems: 'flex-start', }, amount: { fontSize: 28, fontFamily: FONTS.bold, color: COLORS.text, }, subtitle: { fontSize: 14, fontFamily: FONTS.regular, color: COLORS.textLight, marginBottom: SPACING.md, }, chartPlaceholder: { width: '100%', height: 120, backgroundColor: COLORS.background, borderRadius: 8, justifyContent: 'center', alignItems: 'center', }, chartText: { fontSize: 14, fontFamily: FONTS.regular, color: COLORS.textLight, }, }); export default RevenueChart; ``` --- ## 3. REDUX STATE MANAGEMENT PATTERNS ### Store Configuration ```typescript // src/store/store.ts import { configureStore } from '@reduxjs/toolkit'; import { persistStore, persistReducer } from 'redux-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { combineReducers } from '@reduxjs/toolkit'; // Import slices import authSlice from '@/modules/auth/store/authSlice'; import hrSlice from '@/modules/hr/store/hrSlice'; import zohoProjectsSlice from '@/modules/zohoProjects/store/zohoProjectsSlice'; import profileSlice from '@/modules/profile/store/profileSlice'; import uiSlice from '@/shared/store/uiSlice'; const rootReducer = combineReducers({ auth: authSlice.reducer, hr: hrSlice.reducer, zohoProjects: zohoProjectsSlice.reducer, profile: profileSlice.reducer, ui: uiSlice.reducer, }); const persistConfig = { key: 'root', storage: AsyncStorage, whitelist: ['auth', 'hr', 'zohoProjects', 'profile'], blacklist: ['ui'], // Don't persist UI state }; const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: ['persist/FLUSH', 'persist/REHYDRATE', 'persist/PAUSE', 'persist/PERSIST', 'persist/PURGE', 'persist/REGISTER'], }, }), }); export const persistor = persistStore(store); export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch; ``` ### Redux Slice Template (Zoho Projects example) ```typescript // src/modules/zohoProjects/store/zohoProjectsSlice.ts import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import { zohoProjectsAPI } from '../services/zohoProjectsAPI'; import type { ZohoProjectsState, ZohoProject, ZohoProjectsFilters } from './types'; // Initial state const initialState: ZohoProjectsState = { projects: [], loading: false, error: null, filters: { owner: 'all', status: 'all' }, lastUpdated: null, }; // Async thunks export const fetchZohoProjects = createAsyncThunk( 'zohoProjects/fetch', async () => { const response = await zohoProjectsAPI.getProjects(); return response.data; } ); export const updateZohoProject = createAsyncThunk( 'zohoProjects/update', async (project: Partial) => { const response = await zohoProjectsAPI.updateProject(project); return response.data; } ); // Slice const zohoProjectsSlice = createSlice({ name: 'zohoProjects', initialState, reducers: { setFilters: (state, action: PayloadAction>) => { state.filters = { ...state.filters, ...action.payload }; }, clearError: (state) => { state.error = null; }, resetState: () => initialState, }, extraReducers: (builder) => { // add async case reducers here }, }); export default zohoProjectsSlice; ``` ### Redux Slice Template ```typescript // src/modules/finance/store/financeSlice.ts import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import { financeAPI } from '../services/financeAPI'; import type { FinanceState, FinanceData, FinanceFilters } from './types'; // Initial state const initialState: FinanceState = { data: [], loading: false, error: null, filters: { period: 'month', category: 'all', dateRange: null, }, lastUpdated: null, }; // Async thunks export const fetchFinanceData = createAsyncThunk( 'finance/fetchData', async (params?: { refresh?: boolean }) => { const response = await financeAPI.getData(params); return response.data; } ); export const updateFinanceData = createAsyncThunk( 'finance/updateData', async (data: Partial) => { const response = await financeAPI.updateData(data); return response.data; } ); // Slice const financeSlice = createSlice({ name: 'finance', initialState, reducers: { setFilters: (state, action: PayloadAction>) => { state.filters = { ...state.filters, ...action.payload }; }, clearError: (state) => { state.error = null; }, resetState: () => initialState, }, extraReducers: (builder) => { builder // Fetch data cases .addCase(fetchFinanceData