Function bodies 67 total
useAttendanceActions function · typescript · L16-L116 (101 LOC)frontend/src/hooks/attendance/useAttendanceActions.ts
export function useAttendanceActions({
date,
workers,
attendance,
feriadoActual,
fetchAttendanceInfo
}: UseAttendanceActionsProps) {
const { selectedObra } = useObra();
const { hasPermission } = useAuth();
const [saving, setSaving] = useState(false);
// Track the latest data for action callbacks via ref to avoid dependency cycles
const latestData = useRef({ selectedObra, date, workers, attendance, feriadoActual });
useEffect(() => {
latestData.current = { selectedObra, date, workers, attendance, feriadoActual };
}, [selectedObra, date, workers, attendance, feriadoActual]);
const handleSave = useCallback(async () => {
const { selectedObra: currentObra, date: currentDate, workers: currentWorkers, attendance: currentAttendance } = latestData.current;
const isGlobal = !currentObra;
if (isGlobal && !hasPermission('asistencia.tomar.global')) return;
setSaving(true);
try {
const useAttendanceData function · typescript · L11-L274 (264 LOC)frontend/src/hooks/attendance/useAttendanceData.ts
export function useAttendanceData() {
const { selectedObra } = useObra();
const { hasPermission } = useAuth();
const [date, setDate] = useState(new Date().toISOString().split('T')[0]);
const [loading, setLoading] = useState(false);
const [workers, setWorkers] = useState<Trabajador[]>([]);
const [attendance, setAttendance] = useState<Record<number, Partial<Asistencia>>>({});
const [horariosObra, setHorariosObra] = useState<ConfiguracionHorario[]>([]);
const [estados, setEstados] = useState<EstadoAsistencia[]>([]);
const [feriadoActual, setFeriadoActual] = useState<Feriado | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [selectedEmpresaId, setSelectedEmpresaId] = useState<number | null>(null);
const [statusFilter, setStatusFilter] = useState<number | null>(null);
const [alertasFaltas, setAlertasFaltas] = useState<AlertaFalta[]>([]);
const [reportMonth, setReportMonth] = useState((new Date().getMonth() + 1).useAttendanceExport function · typescript · L17-L281 (265 LOC)frontend/src/hooks/attendance/useAttendanceExport.ts
export function useAttendanceExport({
date,
workers,
attendance,
estados,
reportMonth,
reportYear
}: UseAttendanceExportProps) {
const { selectedObra } = useObra();
const { hasPermission } = useAuth();
const latestData = useRef({ selectedObra, date, workers, attendance, estados, reportMonth, reportYear });
useEffect(() => {
latestData.current = { selectedObra, date, workers, attendance, estados, reportMonth, reportYear };
}, [selectedObra, date, workers, attendance, estados, reportMonth, reportYear]);
const handleExportExcel = useCallback(async (returnFile = false) => {
if (!hasPermission('asistencia.exportar_excel')) {
toast.error('No tienes permiso para exportar a Excel');
return null;
}
const {
selectedObra: currentObra,
date: currentDate,
reportMonth: currentMonth,
reportYear: currentYear
} = latestData.currenuseInlineEdit function · typescript · L9-L45 (37 LOC)frontend/src/hooks/inventario/useInlineEdit.ts
export function useInlineEdit({ canEdit, onUpdateStock, onRefresh }: InlineEditOptions) {
const [editingCell, setEditingCell] = useState<string | null>(null);
const [editValue, setEditValue] = useState('');
const startEdit = useCallback((key: string, currentValue: number) => {
if (!canEdit) return;
setEditingCell(key);
setEditValue(String(currentValue || ''));
}, [canEdit]);
const cancelEdit = useCallback(() => {
setEditingCell(null);
setEditValue('');
}, []);
const saveEdit = useCallback(async (itemId: number, obraId: number | null, bodegaId: number | null) => {
const num = parseInt(editValue, 10);
if (isNaN(num) || num < 0) {
cancelEdit();
return;
}
const ok = await onUpdateStock(itemId, obraId, bodegaId, { cantidad: num });
if (ok) onRefresh();
cancelEdit();
}, [editValue, onUpdateStock, onRefresh, cancelEdit]);
retuseInventarioActions function · typescript · L5-L39 (35 LOC)frontend/src/hooks/inventario/useInventarioActions.ts
export function useInventarioActions() {
const updateStock = useCallback(async (
itemId: number,
obraId: number | null,
bodegaId: number | null,
data: { cantidad?: number; valor_arriendo_override?: number | null }
) => {
try {
await api.put('/inventario/stock', {
item_id: itemId,
obra_id: obraId,
bodega_id: bodegaId,
...data
});
toast.success('Stock actualizado');
return true;
} catch (err: any) {
toast.error(err.response?.data?.error || 'Error al actualizar stock');
return false;
}
}, []);
const updateDescuento = useCallback(async (obraId: number, porcentaje: number) => {
try {
await api.put(`/inventario/descuento/obra/${obraId}`, { porcentaje });
toast.success('Descuento actualizado');
return true;
} catch (err: any) {
useInventarioData function · typescript · L78-L124 (47 LOC)frontend/src/hooks/inventario/useInventarioData.ts
export function useInventarioData() {
const [resumen, setResumen] = useState<ResumenData | null>(null);
const [stockObra, setStockObra] = useState<StockObraData | null>(null);
const [stockBodega, setStockBodega] = useState<StockBodegaData | null>(null);
const [loading, setLoading] = useState(false);
const fetchResumen = useCallback(async () => {
setLoading(true);
try {
const res = await api.get<ApiResponse<ResumenData>>('/inventario/resumen');
setResumen(res.data.data);
} catch {
setResumen(null);
} finally {
setLoading(false);
}
}, []);
const fetchStockObra = useCallback(async (obraId: number) => {
setLoading(true);
try {
const res = await api.get<ApiResponse<StockObraData>>(`/inventario/stock/obra/${obraId}`);
setStockObra(res.data.data);
} catch {
setStockObra(null);
} finally {
setLoadinuseItemDetail function · typescript · L29-L128 (100 LOC)frontend/src/hooks/inventario/useItemDetail.ts
export function useItemDetail(): UseItemDetailReturn {
const [selectedItemId, setSelectedItemId] = useState<number | null>(null);
const [itemData, setItemData] = useState<ItemInventario | null>(null);
const [stockLocations, setStockLocations] = useState<StockLocation[]>([]);
const [loading, setLoading] = useState(false);
const [stockLoading, setStockLoading] = useState(false);
// Cache de items ya vistos (no de stock)
const cache = useRef<Map<number, ItemInventario>>(new Map());
const fetchStock = useCallback(async (itemId: number) => {
try {
const res = await api.post<{ data: Record<number, StockLocation[]> }>(
'/inventario/stock-por-items',
{ item_ids: [itemId] }
);
return res.data.data[itemId] || [];
} catch {
return [];
}
}, []);
const fetchItem = useCallback(async (itemId: number): Promise<ItemInventario | null> => {
// Cache hiRepobility — the code-quality scanner for AI-generated software · https://repobility.com
loadHiddenCols function · typescript · L6-L11 (6 LOC)frontend/src/hooks/inventario/useResumenMensualFilters.ts
function loadHiddenCols(): Set<string> {
try {
const raw = localStorage.getItem(STORAGE_KEY);
return raw ? new Set(JSON.parse(raw)) : new Set();
} catch { return new Set(); }
}saveHiddenCols function · typescript · L13-L15 (3 LOC)frontend/src/hooks/inventario/useResumenMensualFilters.ts
function saveHiddenCols(set: Set<string>) {
localStorage.setItem(STORAGE_KEY, JSON.stringify([...set]));
}useResumenMensualFilters function · typescript · L17-L114 (98 LOC)frontend/src/hooks/inventario/useResumenMensualFilters.ts
export function useResumenMensualFilters(data: ResumenData) {
const { obras, bodegas, categorias } = data;
const [collapsedCats, setCollapsedCats] = useState<Set<number>>(new Set());
const [hiddenCols, setHiddenCols] = useState<Set<string>>(loadHiddenCols);
const [hideEmpty, setHideEmpty] = useState(false);
const [search, setSearch] = useState('');
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null);
const [showImages, setShowImages] = useState(false);
useEffect(() => { saveHiddenCols(hiddenCols); }, [hiddenCols]);
const colsWithStock = useMemo(() => {
const set = new Set<string>();
for (const cat of categorias) {
for (const item of cat.items) {
for (const [key, ub] of Object.entries(item.ubicaciones)) {
if (ub && ub.cantidad > 0) set.add(key);
}
}
}
return set;
}, [categorias]);
const visibleObras = ususeTransferencias function · typescript · L29-L181 (153 LOC)frontend/src/hooks/inventario/useTransferencias.ts
export function useTransferencias() {
const [transferencias, setTransferencias] = useState<Transferencia[]>([]);
const [selected, setSelected] = useState<Transferencia | null>(null);
const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(0);
const [discrepancias, setDiscrepancias] = useState<TransferenciaConDiscrepancias[]>([]);
const [selectedDiscrepancia, setSelectedDiscrepancia] = useState<TransferenciaConDiscrepancias | null>(null);
const fetchAll = useCallback(async (query: Record<string, any> = {}) => {
setLoading(true);
try {
const params = new URLSearchParams();
Object.entries(query).forEach(([k, v]) => { if (v != null) params.set(k, String(v)); });
const res = await api.get<TransferenciaListResponse>(`/transferencias?${params}`);
setTransferencias(res.data.data);
setTotal(res.data.total);
} catch {
setTransferencias([]);
useDashboardLayout function · typescript · L11-L96 (86 LOC)frontend/src/hooks/useDashboardLayout.ts
export function useDashboardLayout(userId: number, permisos: Permission[]) {
// Filter widgets the user can see
const allowedWidgets = useMemo(() => {
return WIDGET_REGISTRY.filter(w => {
if (!w.requiredPermission) return true;
const { modulo, accion } = w.requiredPermission!;
const accionMap: Record<string, string> = {
puede_ver: 'ver',
puede_crear: 'crear',
puede_editar: 'editar',
puede_eliminar: 'eliminar'
};
return permisos.includes(`${modulo}.${accionMap[accion] || accion}`);
});
}, [permisos]);
// Load saved order from localStorage, or use default
const getSavedOrder = useCallback((): string[] => {
try {
const saved = localStorage.getItem(`${STORAGE_KEY_PREFIX}${userId}`);
if (saved) {
const parsed: string[] = JSON.parse(saved);
// Only keep IDs that are stiluseFormDirtyProtection function · typescript · L10-L34 (25 LOC)frontend/src/hooks/useFormDirtyProtection.ts
export function useFormDirtyProtection(isDirty: boolean) {
useEffect(() => {
// Evitar que recargue la página o vaya atrás en el navegador
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (isDirty) {
e.preventDefault();
e.returnValue = ''; // Requerido por Chrome
}
};
if (isDirty) {
window.addEventListener('beforeunload', handleBeforeUnload);
// Avisar al ecosistema Modal que estamos sucios
document.body.setAttribute('data-modal-dirty', 'true');
} else {
window.removeEventListener('beforeunload', handleBeforeUnload);
document.body.removeAttribute('data-modal-dirty');
}
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
document.body.removeAttribute('data-modal-dirty');
};
}, [isDirty]);
}cn function · typescript · L7-L9 (3 LOC)frontend/src/utils/cn.ts
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}exportStockObra function · typescript · L48-L280 (233 LOC)frontend/src/utils/exportExcel.ts
export async function exportStockObra(data: StockObraData) {
const wb = new ExcelJS.Workbook();
wb.creator = 'Bóveda LOLS';
wb.created = new Date();
const sheetName = data.obra.nombre.substring(0, 31);
const ws = wb.addWorksheet(sheetName, {
views: [{ showGridLines: false }],
});
// ── Anchos de columna ──
ws.columns = [
{ key: 'nro', width: 7 },
{ key: 'desc', width: 45 },
{ key: 'm2', width: 10 },
{ key: 'arriendo', width: 16 },
{ key: 'unidad', width: 8 },
{ key: 'cantidad', width: 12 },
{ key: 'total', width: 18 },
];
let row: ExcelJS.Row;
// ══════════════════════════════════════════════
// TÍTULO — branding header
// ══════════════════════════════════════════════
ws.mergeCells('A1:G1');
row = ws.getRow(1);
row.height = 36;
row.getCell(1).value = `📦 INVENTARIO — ${data.obra.nombre.toUpperCase()}`;
row.getCell(1).font = boldFonSame scanner, your repo: https://repobility.com — Repobility
exportResumen function · typescript · L285-L523 (239 LOC)frontend/src/utils/exportExcel.ts
export async function exportResumen(data: import('../hooks/inventario/useInventarioData').ResumenData) {
const wb = new ExcelJS.Workbook();
wb.creator = 'Bóveda LOLS';
wb.created = new Date();
const ws = wb.addWorksheet('Resumen General', {
views: [{ showGridLines: false }],
});
// Construir columnas base
const cols: Partial<ExcelJS.Column>[] = [
{ key: 'nro', width: 7 },
{ key: 'desc', width: 45 },
{ key: 'm2', width: 10 },
{ key: 'arriendo', width: 14 },
{ key: 'unidad', width: 8 },
{ key: 'total_qty', width: 12 },
];
// Columnas por obra y bodega
data.obras.forEach(o => cols.push({ key: `obra_${o.id}`, width: 10 }));
data.bodegas.forEach(b => cols.push({ key: `bodega_${b.id}`, width: 10 }));
ws.columns = cols;
let row: ExcelJS.Row;
// ══════════════════════════════════════════════
// TÍTULO
// ══════════════════════════════════════════════
crun function · javascript · L18-L42 (25 LOC)tmp/update_db_states.js
async function run() {
try {
console.log('--- Current States ---');
const [states] = await db.query('SELECT * FROM estados_asistencia');
console.log(JSON.stringify(states, null, 2));
console.log('\n--- Updating 1/2 to JI ---');
const [updateJI] = await db.query("UPDATE estados_asistencia SET nombre = 'Jornada Incompleta (JI)', codigo = 'JI' WHERE codigo = '1/2'");
console.log('Update JI:', updateJI.affectedRows, 'rows');
console.log('\n--- Refining Absence States ---');
// Rename 'Falta' to 'Falta Injustificada' if it exists or consolidate
// We want only 'Falta Justificada' and 'Falta Injustificada'
// Let's see what we have first.
const [updatedStates] = await db.query('SELECT * FROM estados_asistencia');
console.log('\n--- Updated States ---');
console.log(JSON.stringify(updatedStates, null, 2));
process.exit(0);
} catch (e) {
console.error(e);
‹ prevpage 2 / 2