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.7

Published

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

Downloads

118

Readme

@zecimo/websdk

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

Expune widget-uri embed peste aceeași logică de backend ca aplicația principală (pontajele scriu prin HrTimesheetService, deci rezultatul în DB este identic):

  • timesheet — pontaj, varianta pentru contabil: grilă pe taburi, selector de lună </>.
  • employerTimesheet — Pontaj 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).
  • employees — Angajați: lista angajaților firmei (tabel + căutare + buton Angajat nou); click pe un rând → fișa completă a angajatului (aceeași ca la contabil), fără REGES, fără documente contabile și fără editări — doar încărcare de documente și adăugare de rețineri.

Restul vor urma (payroll, 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)
                  ──new zecimoSdk().employees({ sessionToken, elementId })─────────> iframe (angajați)

Același sessionToken funcționează pentru toate widget-urile.

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 sau lista de angajați, schimbă doar metoda (employerTimesheet / employees) — opțiunile sunt identice:

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

// lista de angajați (read-only)
const employees = sdk.employees({
    sessionToken: '<din pasul 2>',
    elementId: 'zecimo-employees',
    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 });

Spațiere

Widget-urile se randează fără padding exterior — conținutul începe de la marginea iframe-ului. Spațierea o decide pagina gazdă, ca widgetul să se așeze la fel ca restul interfeței tale: pune padding pe containerul (elementId) în care montezi, sau injectează-l prin customCss.

/* în pagina gazdă */
#zecimo-employees {
    padding: 1.5rem;
}

Temă

Widget-ul se randează implicit pe temă deschisă. Când pagina gazdă e întunecată, pasează theme: 'dark' — paleta se aplică înainte de prima randare, deci nu clipește alb la încărcare.

sdk.employees({
    sessionToken,
    elementId: 'zecimo-employees',
    theme: 'dark',
});

Fundalul iframe-ului rămâne transparent în ambele teme, deci widgetul se așază pe fundalul paginii tale.

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 (600 → 600px); 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ă. | | theme | 'light' \| 'dark' | nu | Paleta widgetului. Implicit 'light'. Aplicată înainte de bootstrap, ca să nu apară o clipire albă într-o pagină întunecată. | | 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.

sdk.employees(options): EmbeddedWidget

Montează lista Angajați (read-only). Acceptă exact aceleași opțiuni ca sdk.timesheet (sessionToken, elementId, height, theme, customCss, onEvent) și returnează același handle EmbeddedWidget. Emite ready după încărcare și error la eșec.

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.

employees (angajați)

  • Tabel cu angajații firmei: Nume, Prenume, CNP, Tip contract, Salariu brut, Stare contract (Activ / Suspendat / Încetat).
  • Căutare în tabel (Nume, Prenume, CNP, …), fără taburi de filtrare.
  • Click pe un rând deschide fișa angajatului — exact aceeași componentă ca în aplicația contabilului, cu taburile Date personale, Istoric contracte, Timeline, Documente și Rețineri salariale.
  • Fișa e restrânsă față de cea a contabilului:
    • nimic legat de REGES (fără badge-uri de sincronizare, fără trimitere);
    • fără tabul Documente contabile;
    • datele personale, contractele și versiunile sunt doar de citit — nu se pot adăuga, modifica sau șterge contracte/versiuni și nici radia un contract;
    • se pot încărca documente noi (fără editare/ștergere) și se pot adăuga rețineri salariale (fără modificare/ștergere).
  • Buton Angajat nou afișat în header (fără acțiune deocamdată). Buton Înapoi la listă deasupra fișei.
  • Emite ready după încărcare, error la eșec; suportă height / theme / customCss ca celelalte widget-uri.
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) / Mount (angajați)).

Documentație extinsă

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