npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zecimo/websdk

v0.1.4

Published

zecimo Web SDK — embed pontaj și alte widget-uri

Readme

@zecimo/websdk

JavaScript SDK pentru integrarea widget-urilor zecimo în site-uri externe (iframe-based).

Expune două widget-uri de pontaj, ambele scriind prin aceeași logică de backend ca aplicația principală (HrTimesheetService), deci rezultatul în DB este identic:

  • timesheet — varianta pentru contabil: grilă pe taburi, selector de lună </>.
  • employerTimesheetPontaj Angajator: wizard ghidat în 7 pași pentru administratorul firmei (listă de pontaje → completare pas-cu-pas pentru toți angajații → trimitere → rezumat read-only).

Restul vor urma (payroll, employees, etc.).

Instalare

Via script tag (recomandat pentru integrări simple):

<script src="https://embed.zecimo.ro/web-sdk.js"></script>

Sau via npm:

pnpm add @zecimo/websdk
import { zecimoSdk } from '@zecimo/websdk';

Flow-ul de integrare

[backend client]  ──POST /developer/embed/session──> { sessionToken }
                   (header: X-API-Key)

[frontend client] ──new zecimoSdk().timesheet({ sessionToken, elementId })─────────> iframe (contabil)
                  ──new zecimoSdk().employerTimesheet({ sessionToken, elementId })──> iframe (angajator)

Același sessionToken funcționează pentru ambele widget-uri.

1. Obține o cheie API (o dată)

Un admin de workspace creează cheia din Admin → Developer → API Keys. Permisiune necesară: developer.api-keys.manage.

2. Backend: schimbă cheia API contra unui session token

Session tokenul este un JWT legat de (workspaceId, companyId), valabil 12h (configurabil pe server prin EMBED_SESSION_EXPIRY_HOURS). Luna se alege din UI.

curl -X POST https://api.zecimo.ro/developer/embed/session \
  -H 'X-API-Key: prod_…' \
  -H 'Content-Type: application/json' \
  -d '{ "cui": "RO12345678" }'

Răspuns:

{
    "data": {
        "sessionToken": "eyJhbGc…",
        "expiresAt": "2026-05-06T12:34:56.000Z"
    }
}

Erori posibile: 401 (cheie API invalidă), 404 (CUI inexistent în workspace).

3. Frontend: montează widget-ul

<div id="zecimo-timesheet"></div>

<script src="https://embed.zecimo.ro/web-sdk.js"></script>
<script>
    const sdk = new zecimoSdk();

    const widget = sdk.timesheet({
        sessionToken: '<din pasul 2>',
        elementId: 'zecimo-timesheet',
        onEvent: e => console.log(e.type, e.payload),
    });

    // mai târziu:
    // widget.unmount();
</script>

Pentru varianta angajator, schimbă doar metoda — opțiunile sunt identice:

const widget = sdk.employerTimesheet({
    sessionToken: '<din pasul 2>',
    elementId: 'zecimo-timesheet',
    onEvent: e => console.log(e.type, e.payload),
});

Înălțime

Implicit, widget-ul se redimensionează automat după conținut. Dacă vrei o înălțime fixă (widget-ul umple spațiul și derulează intern), pasează height:

// umple containerul (care trebuie să aibă o înălțime definită)
sdk.employerTimesheet({ sessionToken, elementId: 'zecimo-timesheet', height: '100%' });

// înălțime fixă în px
sdk.employerTimesheet({ sessionToken, elementId: 'zecimo-timesheet', height: 600 });

CSS personalizat

Iframe-ul e cross-origin, deci CSS-ul din pagina gazdă nu îl afectează. Pasează customCss ca să injectezi stiluri în widget — de exemplu să mărești font-size-ul general:

sdk.employerTimesheet({
    sessionToken,
    elementId: 'zecimo-timesheet',
    customCss: 'html { font-size: 16px }',
});

API

new zecimoSdk(options?)

| Opțiune | Tip | Default | Descriere | | -------- | -------- | ------------------------- | ------------------------------------------------------------ | | sdkUrl | string | https://embed.zecimo.ro | Origin-ul aplicației de embed (override pentru dev/staging). |

sdk.timesheet(options): EmbeddedWidget

Montează widget-ul de pontaj într-un container din pagina părinte.

| Opțiune | Tip | Obligatoriu | Descriere | | -------------- | -------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | sessionToken | string | da | Token-ul obținut de la POST /developer/embed/session. | | elementId | string | da | ID-ul <div>-ului care va găzdui iframe-ul. | | height | number \| string | nu | Înălțime fixă a iframe-ului. Implicit: auto-resize la conținut (min 450px). number → px (600600px); string → valoare CSS ('100%', '80vh', '600px'). La înălțime fixă, auto-resize-ul e dezactivat, widget-ul umple spațiul (footer fix + scroll intern). Pentru '100%', containerul (elementId) trebuie să aibă o înălțime definită. | | customCss | string | nu | CSS injectat în documentul din iframe (host-ul nu poate stila direct iframe-ul cross-origin). Aplicat ca <style> la finalul <head>, deci suprascrie stilurile widget-ului la specificitate egală. Ex: 'html { font-size: 16px }' mărește font-size-ul general. | | onEvent | (e: zecimoWidgetEvent) => void | nu | Callback pentru evenimente — vezi mai jos. |

Returnează un handle:

interface EmbeddedWidget {
    unmount(): void;
}

sdk.employerTimesheet(options): EmbeddedWidget

Montează wizard-ul Pontaj Angajator (varianta prietenoasă, pentru administratorul firmei). Acceptă exact aceleași opțiuni ca sdk.timesheet (sessionToken, elementId, height, onEvent) și returnează același handle EmbeddedWidget.

Evenimente

type zecimoWidgetEvent =
    | { type: 'ready' }
    | { type: 'resize'; payload: { height: number } }
    | { type: 'saved' }
    | { type: 'submitted' }
    | { type: 'error'; payload: { message: string } };

| Eveniment | Când se emite | | ----------- | ----------------------------------------------------------------------------------------------------------------------- | | ready | După prima încărcare reușită a pontajului. | | resize | La fiecare schimbare de înălțime — doar în modul auto-resize (când nu se pasează height). SDK-ul ajustează iframe-ul. | | saved | Pontajul a fost salvat ca draft (la timesheet manual; la employerTimesheet automat, la fiecare schimbare de pas). | | submitted | Utilizatorul a închis luna (status → submitted). | | error | Eșec la încărcare sau autentificare. |

Comportamentul widget-urilor

timesheet (contabil)

  • Selector de lună integrat — utilizatorul navighează între luni cu < / >.
  • Default este luna trecută (cazul tipic pentru pontaj).
  • Auto-create — dacă nu există pontaj pentru luna selectată, se creează automat la prima încărcare.
  • Editabil până la închidere. La status === 'submitted', formularul devine read-only și butonul Închide dispare.
  • Salvează și Închide sunt expuse în header. Reopen-ul nu este disponibil în embed (acțiune de admin în aplicația principală).

employerTimesheet (angajator)

  • Ecran de start cu lista pontajelor existente pe luni + un card pentru luna în curs (default).
  • Wizard în 7 pași: Prezență (click pe o zi → alegi statusul), Concedii medicale, Ore suplimentare, Tichete, Beneficii, Persoane în întreținere, Verificare.
  • Pornește de la ideea că toți au lucrat normal — angajatorul marchează doar excepțiile.
  • Validare blocantă — nu se poate trece la pasul următor până nu e complet/confirmat.
  • Auto-create la deschiderea unei luni; autosave (status: 'saved') la fiecare schimbare de pas — emite saved.
  • După Trimite (status: 'submitted'), pontajul e închis: se afișează un rezumat read-only și nu mai poate fi editat sau redeschis din embed.

Dezvoltare locală

nx run web-sdk:build         # bundle ESM/CJS/IIFE + declarații TS
nx run sdk:serve             # rulează aplicația host pe :4501

Pentru un test cross-origin rapid, apps/sdk/public/test.html permite paste-ul unui token și montează oricare widget direct (butoanele Mount (contabil) / Mount (angajator)).

Documentație extinsă

Vezi docs/embed-sdk.md pentru flow-ul complet, inclusiv exemple de eroare și semantica auditării.